timesteps 0.9.3 → 0.9.5

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: d9b039e00dac34bb9cf616ea94da9465feb696179d550c1b6f7f511ddf06756f
4
- data.tar.gz: ac64b576636a5d9f45bbca34fd21ea479911a4dc260df2ef5a3adb2b5071ae6d
3
+ metadata.gz: 5c66eb1faabea2c6a697af1b84d2b6e4b36419a7fe67f11a04d448476485697e
4
+ data.tar.gz: d32805b977369e85e0bacdce593275b6f5b30569be978cd74f61a355f9b4c67f
5
5
  SHA512:
6
- metadata.gz: 237fa23fec58640aa1ab3ec9863a8f546016bc978d1b5258e5d5021fb875a55115696a3fb387dd4dc42e47cc561530b3de83604cf4cb7e2f708748183a011c67
7
- data.tar.gz: 978d9e97ced5f11868018fc895fddb261eaf402e937a1371f0395c5f13c857dd1b3122d4e412cc723717fae8860a396fad804ffe316c80277c2516ed79b6de2e
6
+ metadata.gz: f3aba6dae421df42fd53fa5aaaf7b273c9aec3a99d48bd95f9ffd3c0f9b2e6b5a694cc6b8e0c59f3d87355ddb803258dec77d1830280e41e504f85a11ed3b87a
7
+ data.tar.gz: 835e7de674cc0dea1b51571b119bfd55922e8fe4215124048bdc0d519dfcd1ec0bcec176f3a918b844e60e4fe68105c7878b165695f6ec82e1da8e87cdb61d6e
data/Note.ja.md CHANGED
@@ -74,3 +74,17 @@
74
74
  #### 360_day
75
75
 
76
76
  * すべての月が30日であるとした暦
77
+
78
+
79
+ ts = TimeStep.new("6 hours since 2012-01-10 09:00:00")
80
+ ts = ts.shift_origin_with_days(-256.quo(24))
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
data/README.md CHANGED
@@ -9,49 +9,301 @@ Features
9
9
  --------
10
10
 
11
11
  * TimeStep consists of a pair of origin time and a time interval.
12
- * An instance of a TimeStep can be constructed by parsing an expression like "hours since 2001-01-01 00:00:00" (originate from udunits library)
13
- * DateTime object can be obtained from the index value (0 for the origin time).
14
- * The index value can be calculated from the date and time.
15
- * TimeStep can handle special calendar-type time series such as noleap, allleap, and 360_day.
16
- * "24:00:00" can be parsed as a valid timestamp.
17
- * You can pairert the index values between different TimeStep instances using TimeStepConv.
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
15
+ * Treating non-standard calendar-type such as 'noleap', 'allleap', and '360_day'
16
+ * Comparing the index values between different time step definitions
18
17
 
19
- Usage
20
- -----
18
+ Installation
19
+ ------------
21
20
 
22
- ### TimeStep
21
+ gem install timesteps
22
+
23
+ To use the library in your Ruby script,
23
24
 
24
25
  ```ruby
25
26
  require "timesteps"
27
+ ```
28
+
29
+ Description
30
+ -----------
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.
33
+
34
+ #### Time steps
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.
26
37
 
27
- ts = TimeStep.new("3 hours since 2001-01-01 09:00:00", calendar: "standard")
38
+ * "second since 1970-01-01 00:00:00 +00:00"
39
+ * "hour since 2001-01-01 00:00:00 JST"
40
+ * "3 days since 2001-01-01 00:00:00 +00:00"
41
+ * "10 years since 1901-01-01 00:00:00 +00:00"
28
42
 
29
- p ts ### => #<TimeStep definition='3 hours since 2001-01-01 09:00:00.000000000 +00:00' calendar='standard'>
30
-
31
- p ts.origin ### => #<DateTime: 2001-01-01T09:00:00+00:00 ...>
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).
32
44
 
33
- p ts.time_at(1) ### => #<DateTime: 2001-01-01T12:00:00+00:00 ...>
34
- p ts.time_at(8) ### => #<DateTime: 2001-01-02T09:00:00+00:00 ...>
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,
35
47
 
36
- time = DateTime.parse("2001-01-02 09:00:00 +00:00")
37
- p ts.index_at(time) ### => (8/1) <Rational>
48
+ * "2001-01-01 00:00:00" => 0 (0 / 3 hours) ( 0 days)
49
+ * "2001-01-01 03:00:00" => 1 (1 / 3 hours) ((1/8) days)
50
+ * "2001-01-02 00:00:00" => 8 (8 / 3 hours) ( 1 days)
51
+
52
+ This is expressed as a Ruby script.
53
+
54
+ ```ruby
55
+ ts = TimeStep.new("3 hours since 2001-01-01 00:00:00")
56
+ ts.index_at("2001-01-01 00:00:00") ### => 0
57
+ ts.index_at("2001-01-01 03:00:00") ### => 1
58
+ ts.index_at("2001-01-02 00:00:00") ### => 8
59
+ ts.time_at(0) ### => #<DateTime 2001-01-01T00:00:00 ...>
60
+ ts.time_at(1) ### => #<DateTime 2001-01-01T03:00:00 ...>
61
+ 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]
38
65
  ```
39
66
 
40
- ### TimeStepPair
67
+ #### Treatment of year and month units
68
+
69
+ 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.
41
74
 
42
75
  ```ruby
43
- require "timesteps"
76
+ ts = TimeStep.new("year since 2000-01-15 00:00:00")
77
+ ts.index_at("2001-01-14 23:59:59") ### => 0
78
+ ts.index_at("2001-01-15 00:00:00") ### => 1
79
+ ts.index_at("2010-01-14 23:59:59") ### => 9
80
+ ts.index_at("2010-01-15 00:00:00") ### => 10
81
+
82
+ ts = TimeStep.new("month since 2000-01-15 00:00:00")
83
+ ts.index_at("2000-02-14 23:59:59") ### => 0
84
+ ts.index_at("2000-02-15 00:00:00") ### => 1
85
+ ts.index_at("2000-11-14 23:59:59") ### => 9
86
+ ts.index_at("2000-11-15 00:00:00") ### => 10
87
+ ```
88
+
89
+ And, it is not possible to give a fractional index
90
+ for the units of year and month (some methods return a fractional index).
91
+
92
+ ```ruby
93
+ ts = TimeStep.new("year since 2000-01-15 00:00:00")
94
+ ts.time_at(0.5) ### => RuntimeError raised
95
+ # => in `time_at': index for years should be an integer (RuntimeError)
96
+ ```
97
+
98
+ #### Calendars
99
+
100
+ The following calendars, including non-standard calendars, can be handled.
101
+
102
+ * standard, gregorian
103
+ * proleptic_gregorian
104
+ * proleptic_julian, julian
105
+ * noleap, 365_day
106
+ * allleap, 366_day
107
+ * 360_day
108
+
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)).
110
+
111
+ ```ruby
112
+ ts = TimeStep.new("day since 2000-01-01", calendar: "standard")
113
+ ts.time_at(59) ### => #<DateTime: 2000-02-29T00:00:00+00:00 ...>
114
+
115
+ ts = TimeStep.new("day since 2000-01-01", calendar: "noleap")
116
+ ts.time_at(59) ### => #<DateTime::NoLeap: 2000-03-01T00:00:00+00:00 ...>
117
+
118
+ ts = TimeStep.new("day since 2000-01-01", calendar: "360_day")
119
+ ts.time_at(59) ### => #<DateTime::Fixed360Day: 2000-02-30T00:00:00+00:00 ...>
120
+ ```
121
+
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.
123
+
124
+ #### Parsing datetime string
125
+
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.
129
+
130
+ ```ruby
131
+ DateTime.parse_timestamp("1200-01-01") ### "standard"
132
+ DateTime.parse_timestamp("1200-01-01", calendar: "proleptic_gregorian")
133
+ DateTime.parse_timestamp("1200-01-01", calendar: "julian")
134
+ DateTime.parse_timestamp("1200-01-01", calendar: "noleap")
135
+ DateTime.parse_timestamp("1200-01-01", calendar: "allleap")
136
+ DateTime.parse_timestamp("1200-01-01", calendar: "360_day")
137
+ ```
138
+
139
+ If you already have the instance of TimeStep, you can use the method named TimeStep#parse, which is called when it is necessary to convert from the string to the date and time internally.
140
+
141
+ ```ruby
142
+ ts = TimeStep.new("days since 2001-01-01", calendar: "allleap")
143
+ ts.parse("2001-02-29") ### => #<DateTime::AllLeap 2001-02-29T ...>
144
+ ```
145
+
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.
147
+
148
+ ```ruby
149
+ DateTime.parse_timestamp("-0001-01-01")
150
+ # => #<DateTime: -0001-01-01T00:00:00+00:00 ...>
151
+ # B.C. 2
152
+
153
+ DateTime.parse_timestamp("-0001-01-01", bc: true)
154
+ # => #<DateTime: 0000-01-01T00:00:00+00:00 ...>
155
+ # B.C. 1
156
+
157
+ DateTime.parse_timestamp("BC 0001-01-01")
158
+ # => #<DateTime: 0000-01-01T00:00:00+00:00 ...>
159
+ # B.C. 1
160
+ ```
44
161
 
162
+ #### Comparing two time series
163
+
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.
168
+
169
+
170
+ ```ruby
171
+ # Create TimeStepPair object
45
172
  ts1 = TimeStep.new("3 hours since 2001-01-01 21:00:00")
46
173
  ts2 = TimeStep.new("hour since 2001-01-01 09:00:00")
47
174
  pair = TimeStep::Pair.new(ts1, ts2)
48
175
 
49
- p pair ### => <TimeStepConv from='<TimeSteps unit='3 hour since 2001-01-01 21:00:00.000000000' calendar='standard'>' to='<TimeSteps unit='hour since 2001-01-01 09:00:00.000000000' calendar='standard'>'>
50
-
51
- p pair.forward(0) ### 12/1 (12)
52
- p pair.forward(2) ### 18/1 (12 + 2*3h/1h)
176
+ # You can create same object with,
177
+ # pair = TimeStep::Pair.new("3 hours since 2001-01-01 21:00:00",
178
+ # "hour since 2001-01-01 09:00:00")
179
+
180
+ # Forward conversion
181
+ pair.forward(0)
182
+ # => 12
183
+ pair.forward(2)
184
+ # => 18 (12 + 2*3h/1h)
185
+
186
+ # Inverse conversion
187
+ pair.inverse(0)
188
+ # => -4 ((-12h)/3h)
189
+ pair.inverse(2)
190
+ # => (-10/3) ((-12h+2*1h)/3h)
191
+ ```
192
+
193
+ Examples
194
+ ========
195
+
196
+ ### Construct a TimeStep object
197
+
198
+ ```ruby
199
+ # standard calendar
200
+ ts = TimeStep.new("3 hours since 2001-01-01 09:00:00")
201
+
202
+ # noleap calendar
203
+ ts = TimeStep.new("3 hours since 2001-01-01 09:00:00", calendar: "noleap")
204
+
205
+ # specify origin time with DateTime object
206
+ ts = TimeStep.new("3 hours", since: DateTime.parse("2001-01-01 09:00:00"))
207
+ ```
208
+
209
+ ### Attributes of TimeStep object
210
+
211
+ ```ruby
212
+ ts = TimeStep.new("3 hours since 2001-01-01 09:00:00")
213
+
214
+ # specification
215
+ ts.definition
216
+ # => "3 hours since 2001-01-01 09:00:00.000000000 +00:00"
217
+ ts.intervalspec
218
+ # => "3 hours"
219
+ ts.originspec
220
+ # => "2001-01-01 09:00:00.000000000 +00:00"
221
+
222
+ # data for time interval
223
+ ts.numeric
224
+ # => (3/1)
225
+ ts.symbol
226
+ # => :hours
227
+ ts.interval
228
+ # => (10800/1)
229
+
230
+ # origin time
231
+ ts.origin
232
+ # => #<DateTime: 2001-01-01T09:00:00+00:00 ((2451911j,32400s,0n),+0s,2299161j)>
233
+
234
+ # calendar
235
+ ts.calendar
236
+ # => #<TimeStep::Calendar calendar='standard' bc='false'>
237
+ ```
238
+
239
+ ### What is the index value of the time step 'TS' corresponding to time 'T' ?
240
+
241
+ ```ruby
242
+ # hours
243
+ ts = TimeStep.new("6 hours since 2000-01-15 00:00:00")
244
+ ts.index_at("2000-01-25 00:00:00")
245
+ # => 40
246
+ ts.index_at("2000-01-25 03:00:00")
247
+ # => (81/2)
248
+ ```
249
+
250
+ ### What is the time corresponding to index 'I' of time step 'TS' ?
251
+
252
+ ```ruby
253
+ # hours
254
+ ts = TimeStep.new("6 hours since 2000-01-15 00:00:00")
255
+ ts.time_at(40)
256
+ # => #<DateTime: 2000-01-25T00:00:00+00:00 ...>
257
+ ts.time_at(40.5)
258
+ # => #<DateTime: 2000-01-25T03:00:00+00:00 ...>
259
+ ````
260
+
261
+ ### What is the UNIX time stamp for the time corresponding to index 'I' of time step 'TS' ?
262
+
263
+ ```ruby
264
+ ts = TimeStep.new("hour since 2000-01-01 00:00:00")
265
+ unixtime = TimeStep.new("seconds since 1970-01-01 00:00:00")
266
+ pair = TimeStep::Pair.new(ts, unixtime)
267
+
268
+ # UNIX time for index 0
269
+ pair.forward(0)
270
+ # => 946684800
271
+
272
+ # UNIX time for index 10
273
+ pair.forward(10)
274
+ # => 946720800
275
+ ````
276
+
277
+ ### How to get each index for a certain time of multiple time steps having different origin times ?
278
+
279
+ ```ruby
280
+ conv = TimeStep::Converter.new("hour since 2000-01-01 00:00:00", name: "ts0")
281
+ conv["ts1"] = "hour since 2000-01-02 00:00:00"
282
+ conv["ts2"] = "hour since 2000-01-03 00:00:00"
283
+ conv["ts3"] = "hour since 2000-01-04 00:00:00"
284
+
285
+ # index 0 for ts0
286
+ conv.forward(0)
287
+ # => {"ts0"=>0, "ts1"=>-24, "ts2"=>-48, "ts3"=>-72}
288
+
289
+ # index 56 for ts0
290
+ conv.forward(56)
291
+ # => {"ts0"=>56, "ts1"=>32, "ts2"=>8, "ts3"=>-16}
292
+
293
+ # index 81 for ts0
294
+ conv.forward(81)
295
+ # => {"ts0"=>81, "ts1"=>57, "ts2"=>33, "ts3"=>9}
296
+
297
+ # indices [0, 56, 81] for ts0 with time
298
+ conv.forward(0, 56, 81, with_time: true)
299
+ # {"time"=>
300
+ # [#<DateTime: 2000-01-01T00:00:00+00:00 ((2451545j,0s,0n),+0s,2299161j)>,
301
+ # #<DateTime: 2000-01-03T08:00:00+00:00 ((2451547j,28800s,0n),+0s,2299161j)>,
302
+ # #<DateTime: 2000-01-04T09:00:00+00:00 ((2451548j,32400s,0n),+0s,2299161j)>],
303
+ # "ts0"=>[0, 56, 81],
304
+ # "ts1"=>[-24, 32, 57],
305
+ # "ts2"=>[-48, 8, 33],
306
+ # "ts3"=>[-72, -16, 9]}
53
307
 
54
- p pair.inverse(0) ### -4/1 ((-12h)/3h)
55
- p pair.inverse(2) ### -10/3 ((-12h+2*1h)/3h)
56
308
  ```
57
309
 
@@ -1,10 +1,13 @@
1
1
 
2
2
  require "date"
3
3
  require "timesteps/datetimelike"
4
+ require "timesteps/datetime_noleap"
5
+ require "timesteps/datetime_allleap"
6
+ require "timesteps/datetime_360day"
4
7
  require "timesteps/parse_timestamp"
5
8
  require "timesteps/format"
6
- require "timesteps/timestepdatetimeext"
9
+ require "timesteps/timestep_datetime_ext"
7
10
  require "timesteps/calendar"
8
11
  require "timesteps/timestep"
9
- require "timesteps/timesteppair"
10
- require "timesteps/timestepconverter"
12
+ require "timesteps/timestep_pair"
13
+ require "timesteps/timestep_converter"
@@ -12,9 +12,18 @@ class TimeStep
12
12
  # @private
13
13
  SYM_360_day = "360_day".intern
14
14
 
15
+ DateTimeType = {
16
+ :standard => DateTime,
17
+ :proleptic_gregorian => DateTime,
18
+ :proleptic_julian => DateTime,
19
+ :noleap => DateTime::NoLeap,
20
+ :allleap => DateTime::AllLeap,
21
+ SYM_360_day => DateTime::Fixed360Day,
22
+ }
23
+
15
24
  def initialize (calendar = "standard", bc: false)
16
- @name = calendar
17
- @bc = bc
25
+ @name = calendar
26
+ @bc = bc
18
27
  case @name.downcase.intern
19
28
  when :standard, :gregorian
20
29
  @calendar = :standard
@@ -33,6 +42,10 @@ class TimeStep
33
42
 
34
43
  attr_reader :name, :calendar
35
44
 
45
+ def inspect
46
+ "#<TimeStep::Calendar calendar='#{@calendar.to_s}' bc='#{@bc}'>"
47
+ end
48
+
36
49
  def == (other)
37
50
  return @calendar == other.calendar && bc? == other.bc?
38
51
  end
@@ -41,26 +54,26 @@ class TimeStep
41
54
  return @bc ? true : false
42
55
  end
43
56
 
44
- # Parses the given representation of date and time in the calendar,
45
- # and creates an date time instance.
46
- #
47
- # @return [DateTime object]
48
- def parse (timespec)
57
+ def valid_datetime_type? (time)
58
+ return false unless time.is_a?(DateTimeType[@calendar])
49
59
  case @calendar
50
60
  when :standard
51
- time = DateTime.parse_timestamp(timespec, Date::ITALY, bc: @bc)
61
+ return time.start == Date::ITALY
52
62
  when :proleptic_gregorian
53
- time = DateTime.parse_timestamp(timespec, Date::GREGORIAN, bc: @bc)
63
+ return time.start == Date::GREGORIAN
54
64
  when :proleptic_julian
55
- time = DateTime.parse_timestamp(timespec, Date::JULIAN, bc: @bc)
56
- when :noleap
57
- time = DateTime::NoLeap.parse_timestamp(timespec, bc: @bc)
58
- when :allleap
59
- time = DateTime::AllLeap.parse_timestamp(timespec, bc: @bc)
60
- when SYM_360_day
61
- time = DateTime::Fixed360Day.parse_timestamp(timespec, bc: @bc)
62
- end
63
- return time
65
+ return time.start == Date::JULIAN
66
+ else
67
+ return true
68
+ end
69
+ end
70
+
71
+ # Parses the given representation of date and time in the calendar,
72
+ # and creates an date time instance.
73
+ #
74
+ # @return [DateTime object]
75
+ def parse (timespec, format: nil)
76
+ return DateTime.parse_timestamp(timespec, calendar: @calendar.to_s, bc: @bc, format: format)
64
77
  end
65
78
 
66
79
  def jday2date (jday)
@@ -0,0 +1,38 @@
1
+ class DateTime
2
+
3
+ # datetime class represents `360_day` calendar
4
+ #
5
+ class Fixed360Day < DateTimeLike
6
+
7
+ extend DateTimeLikeExtension
8
+
9
+ # Number of days per year
10
+ DPY = 360
11
+
12
+ # Numbers of days per months
13
+ DPM = [0,30,30,30,30,30,30,30,30,30,30,30,30]
14
+
15
+ # Astronomical Julian day number of UNIX epoch
16
+ UNIX_EPOCH_IN_AJD = Rational(4811039,2)
17
+
18
+ def valid_date?
19
+ if @day >= 31
20
+ return false
21
+ end
22
+ if @month != 2
23
+ return Date.valid_date?(@year, @month, @day)
24
+ else
25
+ return ( @day >= 1 and @day <= 30 )
26
+ end
27
+ end
28
+
29
+ private :valid_date?
30
+
31
+ def leap?
32
+ raise NotImplementedError
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+