timesteps 0.9.5 → 0.9.6

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: 0feffe90d4bd2fdd8c478624bc78b67e3a7ab1c5905e236767c7734ba8c6b098
4
+ data.tar.gz: 4e924fa6b145f2b7cb06836d408b05e8bb34e8133797a3b7702ba29033af4400
5
5
  SHA512:
6
- metadata.gz: f3aba6dae421df42fd53fa5aaaf7b273c9aec3a99d48bd95f9ffd3c0f9b2e6b5a694cc6b8e0c59f3d87355ddb803258dec77d1830280e41e504f85a11ed3b87a
7
- data.tar.gz: 835e7de674cc0dea1b51571b119bfd55922e8fe4215124048bdc0d519dfcd1ec0bcec176f3a918b844e60e4fe68105c7878b165695f6ec82e1da8e87cdb61d6e
6
+ metadata.gz: 52773368aa3a11f6ac7e6e2e71b56e8ebb84f78b2c6807ac509f77a8342566cae41fb4b93a1c0c03f4fd1fbed07ae698144fb208f433b5a663a08cbf80ff83cb
7
+ data.tar.gz: 61d67459512fa64a4e7b8967b7300af0c9719b61c2bf4f66c478b5c08a064c38d889f0d2830bb7399d81ba02c516b194ccaab85406855a688204c06347cd6117
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 of this library 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 an 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")
@@ -67,10 +66,7 @@ ts.days_at(8) ### => 1 [Integer]
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,7 +137,7 @@ 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. To parse date-time string of UDUNITS type, `bc` option for `Date.parse_timestamp` method.
147
141
 
148
142
  ```ruby
149
143
  DateTime.parse_timestamp("-0001-01-01")
@@ -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
data/lib/timesteps.rb CHANGED
@@ -1,13 +1,14 @@
1
1
 
2
2
  require "date"
3
3
  require "timesteps/datetimelike"
4
+ require "timesteps/datetimelike_format"
4
5
  require "timesteps/datetime_noleap"
5
6
  require "timesteps/datetime_allleap"
6
7
  require "timesteps/datetime_360day"
7
- require "timesteps/parse_timestamp"
8
- require "timesteps/format"
8
+ require "timesteps/datetime_parse_timestamp"
9
9
  require "timesteps/timestep_datetime_ext"
10
- require "timesteps/calendar"
10
+ require "timesteps/timestep_calendar"
11
11
  require "timesteps/timestep"
12
12
  require "timesteps/timestep_pair"
13
13
  require "timesteps/timestep_converter"
14
+ require "timesteps/timeperiod"
@@ -16,7 +16,6 @@ class DateTime
16
16
  # @return [DateTimeFixedDPY]
17
17
 
18
18
  def self.parse_timestamp (spec, calendar: "standard", bc: false, format: nil)
19
- raise "invalid option 'calendar'" if self != DateTime
20
19
  case calendar.downcase.intern
21
20
  when :standard, :gregorian
22
21
  klass = DateTime
@@ -39,9 +38,13 @@ class DateTime
39
38
  end
40
39
  if format
41
40
  hash = DateTime._strptime(spec, format)
42
- raise "timestring doesn't match with format" unless hash
41
+ raise "date-time string '#{spec}' doesn't match with the given format '#{format}'" unless hash
43
42
  else
44
- hash = DateTime._parse(spec)
43
+ if spec =~ /\A([+\-]?\d{4})(\-(\d{1,2}))?\z/
44
+ hash = { year: $1.to_i, mon: $3 ? $3.to_i : 1, mday: 1 }
45
+ else
46
+ hash = DateTime._parse(spec)
47
+ end
45
48
  end
46
49
  year, month, day, hour, minute, second, sec_fraction, offset =
47
50
  hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)
@@ -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
 
File without changes
@@ -0,0 +1,202 @@
1
+
2
+ class TimePeriod < TimeStep
3
+
4
+ def initialize (spec, since: nil, format: nil, calendar: "standard", bc: false, boundary: "[)" )
5
+ super(spec, since: since, format: format, count: 1, calendar: calendar, bc: bc)
6
+ raise "invalid boundary specification" unless boundary =~ /\A[\[\(][\]\)]\z/
7
+ @boundary = boundary
8
+ @include_start = ( boundary[0] == "[" ) ? true : false
9
+ @include_last = ( boundary[1] == "]" ) ? true : false
10
+ @last = time_at(1)
11
+ end
12
+
13
+ attr_reader :last
14
+
15
+ def start
16
+ return @origin
17
+ end
18
+
19
+ def include_start?
20
+ return @include_start
21
+ end
22
+
23
+ def include_last?
24
+ return @include_last
25
+ end
26
+
27
+ # Returns the value as a string for inspection.
28
+ #
29
+ def inspect
30
+ left_paren = @include_start ? "[" : "("
31
+ right_paren = @include_last ? "]" : ")"
32
+ "#<TimePeriod '#{interval_spec}' #{left_paren}#{start.to_s}, #{last.to_s}#{right_paren} calendar='#{calendar.name}'>"
33
+ end
34
+
35
+ def boundary
36
+ left_paren = @include_start ? "[" : "("
37
+ right_paren = @include_last ? "]" : ")"
38
+ return left_paren + right_paren
39
+ end
40
+
41
+ def include? (other)
42
+ case other
43
+ when TimePeriod
44
+ if ( other.origin < @origin ) ||
45
+ ( other.origin == @origin && ( not include_start? ) && other.include_start? )
46
+ left = false
47
+ else
48
+ left = true
49
+ end
50
+ if ( @last < other.last ) ||
51
+ ( @last == other.last && ( not include_last? ) && other.include_last? )
52
+ right = false
53
+ else
54
+ right = true
55
+ end
56
+ return left & right
57
+ else
58
+ if include_start?
59
+ left = @origin <= other
60
+ else
61
+ left = @origin < other
62
+ end
63
+ if include_last?
64
+ right = other <= @last
65
+ else
66
+ right = other < @last
67
+ end
68
+ return left & right
69
+ end
70
+ end
71
+
72
+ def overlap? (other)
73
+ case other
74
+ when TimePeriod
75
+ if ( @origin < other.last ) ||
76
+ ( @origin == other.last && include_start? && other.include_last? )
77
+ left = true
78
+ else
79
+ left = false
80
+ end
81
+ if ( other.origin < @last ) ||
82
+ ( other.origin == @last && include_last? && other.include_start? )
83
+ right = true
84
+ else
85
+ right = false
86
+ end
87
+ return left && right
88
+ else
89
+ return include?(other)
90
+ end
91
+ end
92
+
93
+ def contact? (other)
94
+ case other
95
+ when TimePeriod
96
+ if @origin <= other.last
97
+ left = true
98
+ else
99
+ left = false
100
+ end
101
+ if other.origin <= @last
102
+ right = true
103
+ else
104
+ right = false
105
+ end
106
+ return left && right
107
+ else
108
+ left = @origin <= other
109
+ right = other <= @last
110
+ return left & right
111
+ end
112
+ end
113
+
114
+ def merge (other)
115
+ raise "can't merge period without contacted period" unless contact?(other)
116
+ if ( other.origin < @origin ) ||
117
+ ( other.origin == @origin && ( not include_start? ) && other.include_start? )
118
+ left = other.origin
119
+ left_boundary = other.include_start? ? "[" : "("
120
+ else
121
+ left = @origin
122
+ left_boundary = include_start? ? "[" : "("
123
+ end
124
+ if ( @last < other.last ) ||
125
+ ( @last == other.last && ( not include_last? ) && other.include_last? )
126
+ right = other.last
127
+ right_boundary = other.include_last? ? "]" : ")"
128
+ else
129
+ right = @last
130
+ right_boundary = include_last? ? ")" : "]"
131
+ end
132
+ ridx = index_at(right)
133
+ lidx = index_at(left)
134
+ numeric = (ridx - lidx) * @numeric
135
+ origin = left
136
+ interval_spec = format("%g %s", numeric, @symbol)
137
+ boundary = left_boundary + right_boundary
138
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: boundary)
139
+ end
140
+
141
+ def clip (other)
142
+ raise "can't clip period without contacted period" unless contact?(other)
143
+ if ( @origin < other.origin ) ||
144
+ ( @origin == other.origin && include_start? && ( not other.include_start? ) )
145
+ left = other.origin
146
+ left_boundary = other.include_start? ? "[" : "("
147
+ else
148
+ left = @origin
149
+ left_boundary = include_start? ? "[" : "("
150
+ end
151
+ if ( other.last < @last ) ||
152
+ ( other.last == @last && include_last? && ( not other.include_last? ) )
153
+ right = other.last
154
+ right_boundary = other.include_last? ? "]" : ")"
155
+ else
156
+ right = @last
157
+ right_boundary = include_last? ? ")" : "]"
158
+ end
159
+ ridx = index_at(right)
160
+ lidx = index_at(left)
161
+ numeric = (ridx - lidx) * @numeric
162
+ origin = left
163
+ interval_spec = format("%g %s", numeric, @symbol)
164
+ bndry = left_boundary + right_boundary
165
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: bndry)
166
+ end
167
+
168
+ def split (time, boundary: ")[")
169
+ raise "can't split period without contacted time" unless contact?(time)
170
+ # left
171
+ numeric = index_at(time) * @numeric
172
+ interval_spec = format("%g %s", numeric, @symbol)
173
+ bndry = (@include_start ? "[" : "(") + boundary[0]
174
+ left_period = TimePeriod.new(interval_spec, since: @origin, calendar: @calendar.name, bc: @bc, boundary: bndry)
175
+ # right
176
+ numeric = (1 - index_at(time)) * @numeric
177
+ interval_spec = format("%g %s", numeric, @symbol)
178
+ bndry = boundary[1] + (@include_last ? "]" : ")")
179
+ right_period = TimePeriod.new(interval_spec, since: @origin, calendar: @calendar.name, bc: @bc, boundary: bndry)
180
+ return left_period, right_period
181
+ end
182
+
183
+ def next
184
+ return TimePeriod.new(interval_spec, since: @last, calendar: @calendar.name, bc: @bc, boundary: boundary)
185
+ end
186
+
187
+ def prev
188
+ origin = index_at(-1)
189
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: boundary)
190
+ end
191
+
192
+ def shift (index)
193
+ return TimePeriod.new(interval_spec, since: time_at(index), calendar: @calendar.name, bc: @bc, boundary: boundary)
194
+ end
195
+
196
+ def shift_with_duration (duration)
197
+ time = @origin + duration
198
+ return TimePeriod.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?, boundary: boundary)
199
+ end
200
+
201
+ end
202
+
@@ -21,22 +21,22 @@ class TimeStep
21
21
  # * minutes, minute, mins, min
22
22
  # * seconds, second, secs, sec, s
23
23
  # * milliseconds, millisecond, msecs, msec, ms
24
- # * microseconds, microsecond, msecs, msec, ms
24
+ # * microseconds, microsecond
25
25
  # If you have already origin time object or general date string,
26
26
  # you can use `since` option,
27
27
  # TimeStep.new("3 hours", since: time)
28
28
  # TimeStep.new("3 hours", since: "2001010121", format: '%Y%m%d%H')
29
- # The option `calendar` specifies the calendar for datetime calculation,
30
- # * standard, gregorian -> DateTime with Date::ITALY as start
31
- # * proleptic_gregorian -> DateTime with Date::GREGORIAN as start
32
- # * proleptic_julian, julian -> DateTime with Date::JULIAN as start
33
- # * noleap, 365_day -> DateTimeNoLeap
34
- # * allleap, 366_day -> DateTimeAllLeap
35
- # * 360_day -> DateTimeFixed360Day
36
- # The option `bc` is a flag whether AD/BC or EC when parsing timestamp for
37
- # negative year.
38
29
  # The option count specifies the number of time steps, which is hint for
39
30
  # some methods (#each etc).
31
+ # The option `calendar` specifies the name of calendar for datetime calculation,
32
+ # * "standard", "gregorian" -> DateTime with Date::ITALY as start
33
+ # * "proleptic_gregorian" -> DateTime with Date::GREGORIAN as start
34
+ # * "proleptic_julian", "julian" -> DateTime with Date::JULIAN as start
35
+ # * "noleap", "365_day" -> DateTimeNoLeap
36
+ # * "allleap", "366_day" -> DateTimeAllLeap
37
+ # * "360_day" -> DateTimeFixed360Day
38
+ # The option `bc` is a flag whether AD/BC or EC when parsing timestamp for
39
+ # negative year.
40
40
  #
41
41
  # @param spec [String]
42
42
  # @option since [DateTime, DateTimeLike, String]
@@ -46,32 +46,38 @@ class TimeStep
46
46
  # @option count [Integer] number of time steps (as hint for some methods)
47
47
  #
48
48
  def initialize (spec, since: nil, format: nil, count: nil, calendar: "standard", bc: false)
49
- case calendar
50
- when String
51
- @calendar = Calendar.new(calendar, bc: bc)
52
- else
53
- @calendar = calendar
54
- end
49
+ @calendar = Calendar.new(calendar, bc: bc)
55
50
  if since
56
51
  parse_interval(spec)
57
52
  case since
58
53
  when String
59
54
  @origin = @calendar.parse(since, format: format)
60
55
  else
61
- raise "mismatch time object with calendar" unless @calendar.valid_datetime_type?(since)
56
+ unless @calendar.valid_datetime_type?(since)
57
+ raise "calendar of the given date-time object doesn't match with the given calendar in option"
58
+ end
62
59
  @origin = since
63
60
  end
64
61
  else
65
- intervalspec, originspec = spec.split(/\s+since\s+/)
66
- parse_interval(intervalspec)
67
- @origin = @calendar.parse(originspec)
62
+ spec1, spec2 = spec.split(/\s+since\s+/)
63
+ parse_interval(spec1)
64
+ @origin = @calendar.parse(spec2)
68
65
  end
69
- @intervalspec = format("%g %s", @numeric, @symbol)
70
- @originspec = @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
71
- @definition = format("%s since %s", @intervalspec, @originspec)
72
66
  @count = count
73
67
  end
74
68
 
69
+ def interval_spec
70
+ return format("%g %s", @numeric, @symbol)
71
+ end
72
+
73
+ def origin_spec
74
+ return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
75
+ end
76
+
77
+ def definition
78
+ format("%s since %s", interval_spec, origin_spec)
79
+ end
80
+
75
81
  # @private
76
82
  PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
77
83
  # @private
@@ -86,7 +92,7 @@ class TimeStep
86
92
  end
87
93
  symbol = $2
88
94
  else
89
- raise "invalid interval specification"
95
+ raise "the interval specification '#{spec}' is invalid."
90
96
  end
91
97
  @interval = @numeric
92
98
  case symbol
@@ -128,10 +134,7 @@ class TimeStep
128
134
 
129
135
  private :parse_interval
130
136
 
131
- attr_reader :definition,
132
- :intervalspec,
133
- :originspec,
134
- :numeric,
137
+ attr_reader :numeric,
135
138
  :symbol,
136
139
  :interval,
137
140
  :origin,
@@ -155,13 +158,15 @@ class TimeStep
155
158
  # @return [DateTime, nil]
156
159
  def limit= (time)
157
160
  if time
158
- @count = (prev_index_of(time) + 1).to_i
161
+ @count = next_index_of(time).to_i
159
162
  else
160
163
  @count = nil
161
164
  end
162
165
  return limit
163
166
  end
164
167
 
168
+ # Sets count by giving maximum time.
169
+ #
165
170
  def set_limit (time, format: nil)
166
171
  case time
167
172
  when String
@@ -193,9 +198,9 @@ class TimeStep
193
198
 
194
199
  def debug_info
195
200
  return {
196
- "definition" => @definition.clone,
197
- "intervalspec" => @intervalspec.clone,
198
- "originspec" => @originspec.clone,
201
+ "definition" => definition,
202
+ "interval_spec" => interval_spec,
203
+ "origin_spec" => origin_spec,
199
204
  "calendar" => @calendar.name,
200
205
  "numeric" => @numeric,
201
206
  "symbol" => @symbol.to_s,
@@ -219,7 +224,7 @@ class TimeStep
219
224
  #
220
225
  # @return [Boolean]
221
226
  def == (other)
222
- return @definition == other.definition && @calendar == other.calendar
227
+ return definition == other.definition && @calendar == other.calendar
223
228
  end
224
229
 
225
230
  def user_to_days (index)
@@ -240,17 +245,16 @@ class TimeStep
240
245
  def time_at (*indices)
241
246
  if indices.size == 1
242
247
  index = indices.first
243
- raise ArgumentError, "index should be numeric" unless index.is_a?(Numeric)
244
- index = index.to_r
248
+ raise ArgumentError, "index argument should be a numeric" unless index.is_a?(Numeric)
245
249
  case @symbol
246
250
  when :years
247
251
  unless index.denominator == 1
248
- raise "index for years should be an integer"
252
+ raise ArgumentError, "index argument should be an integer for years"
249
253
  end
250
254
  return @origin.next_year(index*@numeric)
251
255
  when :months
252
256
  unless index.denominator == 1
253
- raise "index for years should be an integer"
257
+ raise ArgumentError, "index argument should be an integer for months"
254
258
  end
255
259
  return @origin.next_month(index*@numeric)
256
260
  else
@@ -264,22 +268,22 @@ class TimeStep
264
268
  end
265
269
  end
266
270
 
267
- # Calculate the time difference in days from origin at the index.
271
+ # Calculate the duration in days since origin time at the given index.
268
272
  #
269
273
  # @param indices [Array]
270
274
  # @return [DateTime, Array<DateTime>]
271
- def days_at (*indices)
275
+ def duration_at (*indices)
272
276
  if indices.size == 1
273
277
  index = indices.first
274
278
  case @symbol
275
279
  when :years
276
280
  unless index.denominator == 1
277
- raise "index for years should be an integer"
281
+ raise ArgumentError, "index argument should be an integer for years"
278
282
  end
279
283
  days = @origin.next_year(@numeric*index) - @origin
280
284
  when :months
281
285
  unless index.denominator == 1
282
- raise "index for years should be an integer"
286
+ raise ArgumentError, "index argument should be an integer for months"
283
287
  end
284
288
  days = @origin.next_month(@numeric*index) - @origin
285
289
  else
@@ -288,7 +292,7 @@ class TimeStep
288
292
  days = days.to_i if days.denominator == 1
289
293
  return days
290
294
  else
291
- return indices.map{ |index| days_at(index) }
295
+ return indices.map{ |index| duration_at(index) }
292
296
  end
293
297
  end
294
298
 
@@ -319,55 +323,7 @@ class TimeStep
319
323
  return times.map{|time| index_at(time, format: format) }
320
324
  end
321
325
  end
322
-
323
- def range (ge: nil, gt: nil, lt: nil, le: nil)
324
- raise "lower limit is not given" if ge.nil? and gt.nil?
325
- raise "upper limit is not given" if lt.nil? and le.nil?
326
- raise "lower limits are duplicated" if ge and gt
327
- raise "upper limits are duplicated" if lt and le
328
- if ge
329
- case ge
330
- when Numeric
331
- min = ge.floor
332
- else
333
- min = prev_index_of(ge)
334
- end
335
- end
336
- if gt
337
- case gt
338
- when Numeric
339
- if lt.to_r.denominator == 1
340
- min = gt.to_i + 1
341
- else
342
- min = gt.ceil
343
- end
344
- else
345
- min = next_index_of(gt)
346
- end
347
- end
348
- if lt
349
- case lt
350
- when Numeric
351
- if lt.to_r.denominator == 1
352
- max = lt.to_i - 1
353
- else
354
- max = lt.floor
355
- end
356
- else
357
- max = prev_index_of(lt)
358
- end
359
- end
360
- if le
361
- case le
362
- when Numeric
363
- max = le.ceil
364
- else
365
- max = next_index_of(le)
366
- end
367
- end
368
- return (min..max).to_a
369
- end
370
-
326
+
371
327
  # Returns TimeStep object which has origin time determined
372
328
  # by the given `index`.
373
329
  #
@@ -376,12 +332,12 @@ class TimeStep
376
332
  # @return [TimeStep]
377
333
  def shift_origin (index)
378
334
  time = time_at(index)
379
- return TimeStep.new(@intervalspec, since: time, calendar: @calendar)
335
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
380
336
  end
381
337
 
382
- def shift_origin_with_days (days)
383
- time = @origin + days
384
- return TimeStep.new(@intervalspec, since: time, calendar: @calendar)
338
+ def shift_origin_with_duration (duration)
339
+ time = @origin + duration
340
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
385
341
  end
386
342
 
387
343
  # Returns TimeStep object which has origin time specified
@@ -395,14 +351,14 @@ class TimeStep
395
351
  when String
396
352
  time = @calendar.parse(time)
397
353
  end
398
- return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
354
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
399
355
  end
400
356
 
401
357
  include Enumerable
402
358
 
403
359
  def each (limit = nil, incr = 1, &block)
404
360
  if limit.nil?
405
- raise "step method require count" unless @count
361
+ raise "count (or limit) is required by #step" unless @count
406
362
  limit = @count - 1
407
363
  end
408
364
  if block
@@ -419,7 +375,7 @@ class TimeStep
419
375
  end
420
376
 
421
377
  def times (&block)
422
- raise "step method require count" unless @count
378
+ raise "count (or limit) is required by #step" unless @count
423
379
  (0...@count).each(&block)
424
380
  end
425
381
 
@@ -427,15 +383,16 @@ class TimeStep
427
383
  #
428
384
  #
429
385
  def in (unit)
430
- return TimeStep::Pair.new(self, format("%s since %s", unit, @originspec), calendar: @calendar)
386
+ return TimeStep::Pair.new(self, format("%s since %s", unit, origin_spec), calendar: @calendar.name, bc: @calendar.bc?)
431
387
  end
432
388
 
389
+ # Returns next integer index of the given time
433
390
  def next_index_of (time)
434
391
  case time
435
392
  when String
436
393
  time = @calendar.parse(time)
437
394
  end
438
- index = index_at(time).to_r
395
+ index = index_at(time)
439
396
  if index.denominator == 1
440
397
  return index.to_i + 1
441
398
  else
@@ -443,21 +400,70 @@ class TimeStep
443
400
  end
444
401
  end
445
402
 
403
+ # Returns previous integer index of the given time
446
404
  def prev_index_of (time)
447
- return next_index_of(time) - 1
405
+ case time
406
+ when String
407
+ time = @calendar.parse(time)
408
+ end
409
+ index = index_at(time)
410
+ if index.denominator == 1
411
+ return index.to_i - 1
412
+ else
413
+ return index.floor
414
+ end
448
415
  end
449
416
 
417
+ # Returns next time of the given time
450
418
  def next_time_of (time)
451
419
  return time_at(next_index_of(time))
452
420
  end
453
421
 
422
+ # Returns previous time of the given time
454
423
  def prev_time_of (time)
455
424
  return time_at(prev_index_of(time))
456
425
  end
457
426
 
427
+ # Parses date-time string with appropreate calendar
428
+ #
429
+ # @option format [String]
458
430
  def parse (time, format: nil)
459
431
  return @calendar.parse(time, format: format)
460
432
  end
461
433
 
434
+ def index_for_period (period)
435
+ if period.include_start?
436
+ idx1 = index_at(period.origin).ceil
437
+ else
438
+ idx1 = next_index_of(period.origin)
439
+ end
440
+ if period.include_last?
441
+ idx2 = index_at(period.last).floor
442
+ else
443
+ idx2 = prev_index_of(period.last)
444
+ end
445
+ return (idx1..idx2).to_a
446
+ end
447
+
448
+ def period (start, last, boundary: "[)")
449
+ case start
450
+ when Numeric
451
+ idx1 = start
452
+ else
453
+ idx1 = index_at(start)
454
+ end
455
+ case last
456
+ when Numeric
457
+ idx2 = last
458
+ else
459
+ idx2 = index_at(last)
460
+ end
461
+ origin = time_at(idx1)
462
+ numeric = (idx2 - idx1) * @numeric
463
+ interval_spec = format("%g %s", numeric, @symbol)
464
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: boundary)
465
+ end
466
+
467
+
462
468
  end
463
469
 
@@ -21,7 +21,12 @@ class TimeStep
21
21
  SYM_360_day => DateTime::Fixed360Day,
22
22
  }
23
23
 
24
+ # Construct the object
25
+ #
24
26
  def initialize (calendar = "standard", bc: false)
27
+ unless calendar.is_a?(String)
28
+ raise ArgumentError, "argument calendar '#{calendar}' should be string"
29
+ end
25
30
  @name = calendar
26
31
  @bc = bc
27
32
  case @name.downcase.intern
@@ -11,7 +11,7 @@ class TimeStep::Converter
11
11
  when TimeStep
12
12
  @reference = reference
13
13
  else
14
- raise "first argument should be TimeStep or string"
14
+ raise ArgumentError, "reference argument '#{reference}' should be time-step or string"
15
15
  end
16
16
  @pair = {}
17
17
  @target = {}
@@ -26,7 +26,7 @@ class TimeStep::Pair
26
26
  when TimeStep
27
27
  @from = from
28
28
  else
29
- raise "first argument should be TimeStep or string"
29
+ raise ArgumentError, "from argument '#{from}' should be a time-step or a string"
30
30
  end
31
31
 
32
32
  case to
@@ -35,7 +35,7 @@ class TimeStep::Pair
35
35
  when TimeStep
36
36
  @to = to
37
37
  else
38
- raise "second argument should be TimeStep or string"
38
+ raise ArgumentError, "to argument '#{to}' should be a time-step or a string"
39
39
  end
40
40
 
41
41
  @from_origin = @from.origin
@@ -0,0 +1,49 @@
1
+ require "timesteps"
2
+
3
+ class TimeStep::Query
4
+
5
+ def initialize (timestep, format: nil)
6
+ @timestep = timestep
7
+ @format = foramt
8
+ end
9
+
10
+ def __format__ (time)
11
+ return ( @template.nil? ) ? time : time.strftime(@template)
12
+ end
13
+
14
+ private :__format__
15
+
16
+ def just_time? (time)
17
+ return @timestep.index_at(time).denominator == 1
18
+ end
19
+
20
+ def just_before_time_of (time)
21
+ idx = @timestep.index_at(time)
22
+ if idx.denomiator == 1
23
+ time0 = time
24
+ else
25
+ time0 = @timestep.time_at(idx.floor)
26
+ end
27
+ return __format__(time0)
28
+ end
29
+
30
+ def just_after_time_of (time)
31
+ idx = @timestep.index_at(time)
32
+ if idx.denomiator == 1
33
+ time0 = time
34
+ else
35
+ time0 = @timestep.time_at(idx.ceil)
36
+ end
37
+ return __format__(time0)
38
+ end
39
+
40
+ def prev_time_of (time)
41
+ return __format__(@timestep.prev_time_of(time))
42
+ end
43
+
44
+ def next_time_of (time)
45
+ return __format__(@timestep.next_time_of(time))
46
+ end
47
+
48
+ end
49
+
@@ -231,7 +231,7 @@ describe "TimeStep#time_at" do
231
231
  is_asserted_by { ts.time_at(-366) == ts.origin.prev_month(366) }
232
232
 
233
233
  # months timestep don't permit fractional index
234
- expect { ts.time_at(1.5) }.to raise_error(RuntimeError)
234
+ expect { ts.time_at(1.5) }.to raise_error(ArgumentError)
235
235
 
236
236
  end
237
237
 
@@ -250,7 +250,7 @@ describe "TimeStep#time_at" do
250
250
  is_asserted_by { ts.time_at(-366) == ts.origin.prev_year(366) }
251
251
 
252
252
  # years timestep don't permit fractional index
253
- expect { ts.time_at(1.5) }.to raise_error(RuntimeError)
253
+ expect { ts.time_at(1.5) }.to raise_error(ArgumentError)
254
254
 
255
255
  end
256
256
 
@@ -339,7 +339,7 @@ describe "TimeStep#index_at" do
339
339
 
340
340
  end
341
341
 
342
- describe "TimeStep#days_at" do
342
+ describe "TimeStep#duration_at" do
343
343
 
344
344
  example 'seconds' do
345
345
 
@@ -347,25 +347,25 @@ describe "TimeStep#days_at" do
347
347
 
348
348
  ts = TimeStep.new("1 seconds since 2001-02-03 04:05:06 +07:00", calendar: calendar)
349
349
 
350
- is_asserted_by { ts.days_at( 1 ) == 1.quo(86400) }
351
- is_asserted_by { ts.days_at( 100 ) == 100.quo(86400) }
352
- is_asserted_by { ts.days_at( 0.5 ) == 0.5.to_r.quo(86400) }
353
- is_asserted_by { ts.days_at( -0.5 ) == -0.5.to_r.quo(86400) }
350
+ is_asserted_by { ts.duration_at( 1 ) == 1.quo(86400) }
351
+ is_asserted_by { ts.duration_at( 100 ) == 100.quo(86400) }
352
+ is_asserted_by { ts.duration_at( 0.5 ) == 0.5.to_r.quo(86400) }
353
+ is_asserted_by { ts.duration_at( -0.5 ) == -0.5.to_r.quo(86400) }
354
354
 
355
355
  end
356
356
 
357
357
  end
358
358
 
359
- example 'days' do
359
+ example 'duration' do
360
360
 
361
361
  CALENDAR_NAMES.each do |calendar|
362
362
 
363
363
  ts = TimeStep.new("1 days since 2001-02-03 04:05:06 +07:00", calendar: calendar)
364
364
 
365
- is_asserted_by { ts.days_at( 1 ) == 1 }
366
- is_asserted_by { ts.days_at( 100 ) == 100 }
367
- is_asserted_by { ts.days_at( 0.5 ) == 0.5 }
368
- is_asserted_by { ts.days_at( -0.5 ) == -0.5 }
365
+ is_asserted_by { ts.duration_at( 1 ) == 1 }
366
+ is_asserted_by { ts.duration_at( 100 ) == 100 }
367
+ is_asserted_by { ts.duration_at( 0.5 ) == 0.5 }
368
+ is_asserted_by { ts.duration_at( -0.5 ) == -0.5 }
369
369
 
370
370
  end
371
371
 
@@ -377,10 +377,10 @@ describe "TimeStep#days_at" do
377
377
 
378
378
  ts = TimeStep.new("1 months since 2001-02-03 04:05:06 +07:00", calendar: calendar)
379
379
 
380
- is_asserted_by { ts.days_at( 1 ) == ts.origin.next_month - ts.origin }
381
- is_asserted_by { ts.days_at( 100 ) == ts.origin.next_month(100) - ts.origin }
382
- is_asserted_by { ts.days_at( -1 ) == ts.origin.prev_month - ts.origin }
383
- is_asserted_by { ts.days_at( -100 ) == ts.origin.prev_month(100) - ts.origin }
380
+ is_asserted_by { ts.duration_at( 1 ) == ts.origin.next_month - ts.origin }
381
+ is_asserted_by { ts.duration_at( 100 ) == ts.origin.next_month(100) - ts.origin }
382
+ is_asserted_by { ts.duration_at( -1 ) == ts.origin.prev_month - ts.origin }
383
+ is_asserted_by { ts.duration_at( -100 ) == ts.origin.prev_month(100) - ts.origin }
384
384
 
385
385
  end
386
386
 
@@ -392,10 +392,10 @@ describe "TimeStep#days_at" do
392
392
 
393
393
  ts = TimeStep.new("1 years since 2001-02-03 04:05:06 +07:00", calendar: calendar)
394
394
 
395
- is_asserted_by { ts.days_at( 1 ) == ts.origin.next_year - ts.origin }
396
- is_asserted_by { ts.days_at( 100 ) == ts.origin.next_year(100) - ts.origin }
397
- is_asserted_by { ts.days_at( -1 ) == ts.origin.prev_year - ts.origin }
398
- is_asserted_by { ts.days_at( -100 ) == ts.origin.prev_year(100) - ts.origin }
395
+ is_asserted_by { ts.duration_at( 1 ) == ts.origin.next_year - ts.origin }
396
+ is_asserted_by { ts.duration_at( 100 ) == ts.origin.next_year(100) - ts.origin }
397
+ is_asserted_by { ts.duration_at( -1 ) == ts.origin.prev_year - ts.origin }
398
+ is_asserted_by { ts.duration_at( -100 ) == ts.origin.prev_year(100) - ts.origin }
399
399
 
400
400
  end
401
401
 
@@ -63,7 +63,7 @@ describe "TimeStep::Pair#forward" do
63
63
  is_asserted_by { pair.forward(1) == 4 }
64
64
  is_asserted_by { pair.forward(10) == 31 }
65
65
 
66
- expect { pair.forward(1.5) }.to raise_error(RuntimeError)
66
+ expect { pair.forward(1.5) }.to raise_error(ArgumentError)
67
67
  end
68
68
 
69
69
  example "different years" do
@@ -77,7 +77,7 @@ describe "TimeStep::Pair#forward" do
77
77
  is_asserted_by { pair.forward(1) == 4 }
78
78
  is_asserted_by { pair.forward(10) == 31 }
79
79
 
80
- expect { pair.forward(1.5) }.to raise_error(RuntimeError)
80
+ expect { pair.forward(1.5) }.to raise_error(ArgumentError)
81
81
 
82
82
  end
83
83
 
data/timesteps.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  Gem::Specification::new do |s|
3
- version = "0.9.5"
3
+ version = "0.9.6"
4
4
 
5
5
  files = Dir.glob("**/*") - [
6
6
  Dir.glob("timesteps-*.gem"),
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timesteps
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroki Motoyoshi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-08 00:00:00.000000000 Z
11
+ date: 2020-05-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: " A library for time conversion and intercomparison of multiple time
14
14
  series data \n in the case of handling time series data of the type that specifies
@@ -27,18 +27,20 @@ files:
27
27
  - README.md
28
28
  - Rakefile
29
29
  - lib/timesteps.rb
30
- - lib/timesteps/calendar.rb
31
30
  - lib/timesteps/datetime_360day.rb
32
31
  - lib/timesteps/datetime_allleap.rb
33
32
  - lib/timesteps/datetime_noleap.rb
33
+ - lib/timesteps/datetime_parse_timestamp.rb
34
34
  - lib/timesteps/datetimelike.rb
35
- - lib/timesteps/format.rb
35
+ - lib/timesteps/datetimelike_format.rb
36
36
  - lib/timesteps/grads.rb
37
- - lib/timesteps/parse_timestamp.rb
37
+ - lib/timesteps/timeperiod.rb
38
38
  - lib/timesteps/timestep.rb
39
+ - lib/timesteps/timestep_calendar.rb
39
40
  - lib/timesteps/timestep_converter.rb
40
41
  - lib/timesteps/timestep_datetime_ext.rb
41
42
  - lib/timesteps/timestep_pair.rb
43
+ - lib/timesteps/timestep_query.rb
42
44
  - spec/allleap_spec.rb
43
45
  - spec/fixed360day_spec.rb
44
46
  - spec/noleap_spec.rb