timesteps 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Note.ja.md +9 -0
- data/README.md +5 -5
- data/lib/timesteps.rb +2 -0
- data/lib/timesteps/datetime_parse_timestamp.rb +21 -12
- data/lib/timesteps/datetime_timestep.rb +64 -0
- data/lib/timesteps/datetimelike.rb +6 -17
- data/lib/timesteps/grads.rb +39 -18
- data/lib/timesteps/timeperiod.rb +50 -47
- data/lib/timesteps/timestep.rb +477 -247
- data/lib/timesteps/timestep_calendar.rb +49 -29
- data/lib/timesteps/timestep_converter.rb +19 -22
- data/lib/timesteps/timestep_datetime_ext.rb +6 -16
- data/lib/timesteps/timestep_pair.rb +37 -24
- data/lib/timesteps/timestep_range.rb +150 -0
- data/spec/allleap_spec.rb +4 -15
- data/spec/fixed360day_spec.rb +4 -15
- data/spec/noleap_spec.rb +2 -13
- data/spec/timestep_spec.rb +4 -27
- data/timesteps.gemspec +1 -1
- metadata +4 -2
@@ -1,4 +1,6 @@
|
|
1
1
|
|
2
|
+
autoload :TZInfo, "tzinfo"
|
3
|
+
|
2
4
|
class TimeStep
|
3
5
|
|
4
6
|
class Calendar
|
@@ -23,12 +25,11 @@ class TimeStep
|
|
23
25
|
|
24
26
|
# Construct the object
|
25
27
|
#
|
26
|
-
def initialize (calendar = "standard",
|
28
|
+
def initialize (calendar = "standard", tz: nil)
|
27
29
|
unless calendar.is_a?(String)
|
28
30
|
raise ArgumentError, "argument calendar '#{calendar}' should be string"
|
29
31
|
end
|
30
32
|
@name = calendar
|
31
|
-
@bc = bc
|
32
33
|
case @name.downcase.intern
|
33
34
|
when :standard, :gregorian
|
34
35
|
@calendar = :standard
|
@@ -43,22 +44,30 @@ class TimeStep
|
|
43
44
|
when SYM_360_day
|
44
45
|
@calendar = SYM_360_day
|
45
46
|
end
|
47
|
+
if tz
|
48
|
+
raise "'standard' calendar needed for tz" unless @calendar == :standard
|
49
|
+
case tz
|
50
|
+
when nil
|
51
|
+
when String
|
52
|
+
@tz = TZInfo::Timezone.get(tz)
|
53
|
+
when TZInfo::Timezone
|
54
|
+
@tz = tz
|
55
|
+
else
|
56
|
+
raise "invalid tz specification"
|
57
|
+
end
|
58
|
+
end
|
46
59
|
end
|
47
60
|
|
48
|
-
attr_reader :name, :calendar
|
49
|
-
|
61
|
+
attr_reader :name, :calendar, :tz
|
62
|
+
|
50
63
|
def inspect
|
51
|
-
"#<TimeStep::Calendar calendar='#{@calendar.to_s}'
|
64
|
+
"#<TimeStep::Calendar calendar='#{@calendar.to_s}'>"
|
52
65
|
end
|
53
66
|
|
54
67
|
def == (other)
|
55
|
-
return @calendar == other.calendar
|
56
|
-
end
|
57
|
-
|
58
|
-
def bc?
|
59
|
-
return @bc ? true : false
|
68
|
+
return @calendar == other.calendar
|
60
69
|
end
|
61
|
-
|
70
|
+
|
62
71
|
def valid_datetime_type? (time)
|
63
72
|
return false unless time.is_a?(DateTimeType[@calendar])
|
64
73
|
case @calendar
|
@@ -77,10 +86,31 @@ class TimeStep
|
|
77
86
|
# and creates an date time instance.
|
78
87
|
#
|
79
88
|
# @return [DateTime object]
|
80
|
-
def parse (timespec, format: nil)
|
81
|
-
return DateTime.parse_timestamp(timespec, calendar: @calendar.to_s,
|
89
|
+
def parse (timespec, format: nil, offset: nil)
|
90
|
+
return DateTime.parse_timestamp(timespec, calendar: @calendar.to_s, format: format, tz: @tz, offset: offset)
|
82
91
|
end
|
83
|
-
|
92
|
+
|
93
|
+
def time_new (year, month, day, hour=0, min=0, sec=0, offset=0)
|
94
|
+
case @calendar
|
95
|
+
when :standard
|
96
|
+
time = DateTime.new(year, month, day, hour, min, sec, offset, Date::ITALY)
|
97
|
+
when :proleptic_gregorian
|
98
|
+
time = DateTime.new(year, month, day, hour, min, sec, offset, Date::GREGORIAN)
|
99
|
+
when :proleptic_julian
|
100
|
+
time = DateTime.new(year, month, day, hour, min, sec, offset, Date::JULIAN)
|
101
|
+
when :noleap
|
102
|
+
time = DateTime::NoLeap.new(year, month, day, hour, min, sec, offset)
|
103
|
+
when :allleap
|
104
|
+
time = DateTime::AllLeap.new(year, month, day, hour, min, sec, offset)
|
105
|
+
when SYM_360_day
|
106
|
+
time = DateTime::Fixed360Day.new(year, month, day, hour, min, sec, offset)
|
107
|
+
end
|
108
|
+
if @tz
|
109
|
+
time = @tz.to_local(time)
|
110
|
+
end
|
111
|
+
return time
|
112
|
+
end
|
113
|
+
|
84
114
|
def jday2date (jday)
|
85
115
|
case @calendar
|
86
116
|
when :standard
|
@@ -96,27 +126,17 @@ class TimeStep
|
|
96
126
|
when SYM_360_day
|
97
127
|
time = DateTime::Fixed360Day.jd(jday, 0, 0, 0, 0)
|
98
128
|
end
|
129
|
+
if @tz
|
130
|
+
time = @tz.to_local(time)
|
131
|
+
end
|
99
132
|
return time
|
100
133
|
end
|
101
134
|
|
102
135
|
def date2jday (year, month, day)
|
103
|
-
|
104
|
-
when :standard
|
105
|
-
time = DateTime.new(year, month, day, 0, 0, 0, 0, Date::ITALY)
|
106
|
-
when :proleptic_gregorian
|
107
|
-
time = DateTime.new(year, month, day, 0, 0, 0, 0, Date::GREGORIAN)
|
108
|
-
when :proleptic_julian
|
109
|
-
time = DateTime.new(year, month, day, 0, 0, 0, 0, Date::JULIAN)
|
110
|
-
when :noleap
|
111
|
-
time = DateTime::NoLeap.new(year, month, day, 0, 0, 0, 0)
|
112
|
-
when :allleap
|
113
|
-
time = DateTime::AllLeap.new(year, month, day, 0, 0, 0, 0)
|
114
|
-
when SYM_360_day
|
115
|
-
time = DateTime::Fixed360Day.new(year, month, day, 0, 0, 0, 0)
|
116
|
-
end
|
117
|
-
return time.jd
|
136
|
+
return time_new(year, month, day).jd
|
118
137
|
end
|
119
138
|
|
120
139
|
end
|
121
140
|
|
122
141
|
end
|
142
|
+
|
@@ -2,12 +2,11 @@ require "timesteps"
|
|
2
2
|
|
3
3
|
class TimeStep::Converter
|
4
4
|
|
5
|
-
def initialize (reference, name: "reference", calendar: "standard"
|
5
|
+
def initialize (reference, name: "reference", calendar: "standard")
|
6
6
|
@calendar = calendar
|
7
|
-
@bc = bc
|
8
7
|
case reference
|
9
8
|
when String
|
10
|
-
@reference = TimeStep.new(reference, calendar: calendar
|
9
|
+
@reference = TimeStep.new(reference, calendar: calendar)
|
11
10
|
when TimeStep
|
12
11
|
@reference = reference
|
13
12
|
else
|
@@ -21,9 +20,9 @@ class TimeStep::Converter
|
|
21
20
|
# Append or reset new target time step for the given name.
|
22
21
|
#
|
23
22
|
def add_timestep (spec, name:)
|
24
|
-
@pair[name] = TimeStep::Pair.new(@reference, spec, calendar: @calendar
|
23
|
+
@pair[name] = TimeStep::Pair.new(@reference, spec, calendar: @calendar)
|
25
24
|
@target[name] = @pair[name].to
|
26
|
-
return @
|
25
|
+
return @pair[name]
|
27
26
|
end
|
28
27
|
|
29
28
|
# Append or reset new target time step for the given name.
|
@@ -35,14 +34,14 @@ class TimeStep::Converter
|
|
35
34
|
# Return target time step of the given name.
|
36
35
|
#
|
37
36
|
def [] (name)
|
38
|
-
return @
|
37
|
+
return @pair[name]
|
39
38
|
end
|
40
39
|
|
41
40
|
# Relete target of the given name.
|
42
41
|
#
|
43
42
|
def delete (name)
|
44
|
-
@pair.delete(name)
|
45
43
|
@target.delete(name)
|
44
|
+
@pair.delete(name)
|
46
45
|
end
|
47
46
|
|
48
47
|
# Returns the time represented by the given index as DateTime object
|
@@ -56,11 +55,18 @@ class TimeStep::Converter
|
|
56
55
|
|
57
56
|
# Converts index refering `from` timestep to index refering `to`timestep.
|
58
57
|
#
|
59
|
-
def forward (*indices, with_time:
|
58
|
+
def forward (*indices, with_time: nil)
|
60
59
|
if with_time
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
case with_time
|
61
|
+
when String
|
62
|
+
hash = {
|
63
|
+
"time" => time_at(*indices).map{|t| t.strftime(with_time) }
|
64
|
+
}
|
65
|
+
else
|
66
|
+
hash = {
|
67
|
+
"time" => time_at(*indices)
|
68
|
+
}
|
69
|
+
end
|
64
70
|
else
|
65
71
|
hash = {}
|
66
72
|
end
|
@@ -70,8 +76,8 @@ class TimeStep::Converter
|
|
70
76
|
return hash
|
71
77
|
end
|
72
78
|
|
73
|
-
def range (
|
74
|
-
indices = @reference.range(
|
79
|
+
def range (other, with_time: false)
|
80
|
+
indices = @reference.range(other)
|
75
81
|
return forward(*indices, with_time: with_time)
|
76
82
|
end
|
77
83
|
|
@@ -80,14 +86,5 @@ class TimeStep::Converter
|
|
80
86
|
return forward(*indices, with_time: with_time)
|
81
87
|
end
|
82
88
|
|
83
|
-
def valid? (*indices)
|
84
|
-
hash = {}
|
85
|
-
@pair.each do |name, conv|
|
86
|
-
hash[name] = conv.to.valid?(*conv.forward(*indices))
|
87
|
-
end
|
88
|
-
return hash
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
89
|
end
|
93
90
|
|
@@ -46,28 +46,18 @@ class TimeStep
|
|
46
46
|
#
|
47
47
|
# @return [Integer]
|
48
48
|
def difference_in_years (other)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
if self.year < other.year && self.compare_md(other) > 0
|
54
|
-
extra = 1
|
55
|
-
end
|
56
|
-
return self.year - other.year + extra
|
49
|
+
my = self
|
50
|
+
other = other.new_offset(self.offset)
|
51
|
+
return my.year - other.year + my.compare_md(other).quo(2)
|
57
52
|
end
|
58
53
|
|
59
54
|
# Calculate difference between the object and other object in months.
|
60
55
|
#
|
61
56
|
# @return [Integer]
|
62
57
|
def difference_in_months (other)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
if self.month < other.month && self.compare_d(other) > 0
|
68
|
-
extra = 1
|
69
|
-
end
|
70
|
-
return 12*(self.year - other.year) + self.month - other.month + extra
|
58
|
+
my = self
|
59
|
+
other = other.new_offset(self.offset)
|
60
|
+
return 12*(my.year - other.year) + my.month - other.month + my.compare_d(other).quo(2)
|
71
61
|
end
|
72
62
|
|
73
63
|
end
|
@@ -8,21 +8,19 @@ class TimeStep::Pair
|
|
8
8
|
# The `from` and `to` arguments are either TimeStep or a string representing
|
9
9
|
# the time step definition ("<INTERVAL> since <TIME>").
|
10
10
|
# If a string representing the time step definition is given as an argument
|
11
|
-
# `from` or `to`, the options `calendar`
|
11
|
+
# `from` or `to`, the options `calendar` are used to construct
|
12
12
|
# TimeStep. The option `calendar` specifies the calendar for datetime
|
13
|
-
# calculation.
|
14
|
-
# timestamp for negative year.
|
13
|
+
# calculation.
|
15
14
|
#
|
16
15
|
# @param from [TimeStep, String]
|
17
16
|
# @param to [TimeStep, String]
|
18
17
|
# @option calendar [String, TimeStep::Calendar]
|
19
|
-
# @option bc [Boolean]
|
20
18
|
#
|
21
|
-
def initialize (from, to, calendar: "standard"
|
19
|
+
def initialize (from, to, calendar: "standard")
|
22
20
|
|
23
21
|
case from
|
24
22
|
when String
|
25
|
-
@from = TimeStep.new(from, calendar: calendar
|
23
|
+
@from = TimeStep.new(from, calendar: calendar)
|
26
24
|
when TimeStep
|
27
25
|
@from = from
|
28
26
|
else
|
@@ -31,7 +29,7 @@ class TimeStep::Pair
|
|
31
29
|
|
32
30
|
case to
|
33
31
|
when String
|
34
|
-
@to = TimeStep.new(to, calendar: calendar
|
32
|
+
@to = TimeStep.new(to, calendar: calendar)
|
35
33
|
when TimeStep
|
36
34
|
@to = to
|
37
35
|
else
|
@@ -66,13 +64,17 @@ class TimeStep::Pair
|
|
66
64
|
"#<TimeStep::Pair from='#{from.inspect}' to='#{to.inspect}'>"
|
67
65
|
end
|
68
66
|
|
67
|
+
def invert
|
68
|
+
return TimeStep::Pair.new(@to, @from)
|
69
|
+
end
|
70
|
+
|
69
71
|
# Returns true if other has same contents of `from` and `to` as self has.
|
70
72
|
#
|
71
73
|
# @return [Boolean]
|
72
74
|
def == (other)
|
73
75
|
return @from == other.from && @to == other.to
|
74
76
|
end
|
75
|
-
|
77
|
+
|
76
78
|
# Returns the time represented by the given index as DateTime object
|
77
79
|
#
|
78
80
|
# @param indices [Numeric,Array<Numeric>]
|
@@ -85,10 +87,16 @@ class TimeStep::Pair
|
|
85
87
|
# Converts index refering `from` timestep to index refering `to`timestep.
|
86
88
|
#
|
87
89
|
def forward (*indices, &block)
|
88
|
-
if indices.size == 1
|
90
|
+
if indices.empty? || indices.size == 1 && indices.first.is_a?(Range)
|
91
|
+
return forward(*@from.range(*indices), &block)
|
92
|
+
elsif indices.size == 1
|
89
93
|
index = indices.first.to_r
|
90
94
|
if @numeric_conversion
|
91
|
-
|
95
|
+
if @to_interval == 0
|
96
|
+
value = 0
|
97
|
+
else
|
98
|
+
value = (index*@from_interval + @difference).quo(@to_interval)
|
99
|
+
end
|
92
100
|
else
|
93
101
|
case @from_symbol
|
94
102
|
when :years, :months
|
@@ -97,12 +105,14 @@ class TimeStep::Pair
|
|
97
105
|
target = @from_origin + index*(@from_interval.quo(86400))
|
98
106
|
end
|
99
107
|
case @to_symbol
|
100
|
-
when :years
|
101
|
-
|
108
|
+
when :years
|
109
|
+
diff = target.difference_in_years(@to_origin)
|
110
|
+
value = diff.quo(@to.numeric)
|
102
111
|
when :months
|
103
|
-
|
112
|
+
diff = target.difference_in_months(@to_origin)
|
113
|
+
value = diff.quo(@to.numeric)
|
104
114
|
else
|
105
|
-
value = (target.ajd - @to_origin.ajd).quo(@to_interval)
|
115
|
+
value = (target.ajd - @to_origin.ajd).quo(@to_interval)*86400
|
106
116
|
end
|
107
117
|
end
|
108
118
|
value = value.to_i if value.denominator == 1
|
@@ -126,18 +136,19 @@ class TimeStep::Pair
|
|
126
136
|
return forward(*indices) {|index| @to.time_at(index) }
|
127
137
|
end
|
128
138
|
|
129
|
-
def range (ge: nil, gt: nil, lt: nil, le: nil)
|
130
|
-
indices = @from.range(ge: ge, gt: gt, lt: lt, le: le)
|
131
|
-
return forward(*indices)
|
132
|
-
end
|
133
|
-
|
134
139
|
# Converts index refering `to` timestep to index refering `from` timestep.
|
135
140
|
#
|
136
141
|
def inverse (*indices, &block)
|
137
|
-
if indices.size == 1
|
142
|
+
if indices.empty? || indices.size == 1 && indices.first.is_a?(Range)
|
143
|
+
return inverse(*@to.range(*indices), &block)
|
144
|
+
elsif indices.size == 1
|
138
145
|
index = indices.first.to_r
|
139
146
|
if @numeric_conversion
|
140
|
-
|
147
|
+
if @from_interval == 0
|
148
|
+
value = 0
|
149
|
+
else
|
150
|
+
value = (index*@to_interval - @difference).quo(@from_interval)
|
151
|
+
end
|
141
152
|
else
|
142
153
|
case @to_symbol
|
143
154
|
when :years, :months
|
@@ -147,11 +158,13 @@ class TimeStep::Pair
|
|
147
158
|
end
|
148
159
|
case @from_symbol
|
149
160
|
when :years
|
150
|
-
|
161
|
+
diff = target.difference_in_years(@from_origin)
|
162
|
+
value = diff.quo(@from.numeric)
|
151
163
|
when :months
|
152
|
-
|
164
|
+
diff = target.difference_in_months(@from_origin)
|
165
|
+
value = diff.quo(@from.numeric)
|
153
166
|
else
|
154
|
-
value = (target.ajd - @from_origin.ajd).quo(@from_interval)
|
167
|
+
value = (target.ajd - @from_origin.ajd).quo(@from_interval)*86400
|
155
168
|
end
|
156
169
|
end
|
157
170
|
value = value.to_i if value.denominator == 1
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
class TimeStep::Range
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize (timestep, start = nil, last = nil, count: nil, ends: "[]")
|
8
|
+
|
9
|
+
raise "'ends' option should be one of '[]', '()', '(]', '[)" unless ends =~ /\A[\[\(][\]\)]\z/
|
10
|
+
include_start = ends[0] == "["
|
11
|
+
include_last = ends[1] == "]"
|
12
|
+
|
13
|
+
if last
|
14
|
+
case start
|
15
|
+
when Numeric
|
16
|
+
raise "start and last types doesn't match" unless last.is_a?(Numeric)
|
17
|
+
when DateTime, DateTimeLike, String
|
18
|
+
if last.is_a?(Integer)
|
19
|
+
start = timestep.index_at(start)
|
20
|
+
last = start + last - 1
|
21
|
+
else
|
22
|
+
raise "start and last types doesn't match" unless last.is_a?(start.class)
|
23
|
+
start = timestep.index_at(start)
|
24
|
+
last = timestep.index_at(last)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
raise "unknown argument"
|
28
|
+
end
|
29
|
+
else
|
30
|
+
case start
|
31
|
+
when Integer
|
32
|
+
count = start
|
33
|
+
start = 0
|
34
|
+
last = count - 1
|
35
|
+
when String
|
36
|
+
raise "count should be set" unless count.is_a?(Integer)
|
37
|
+
start = timestep.index_at(start)
|
38
|
+
last = start + count - 1
|
39
|
+
when TimePeriod
|
40
|
+
period = range
|
41
|
+
return initialize(timestep, period.origin..period.last, ends: period.ends)
|
42
|
+
else
|
43
|
+
raise "unknown argument"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if include_start
|
48
|
+
@start = start.ceil
|
49
|
+
else
|
50
|
+
@start = start.floor + 1
|
51
|
+
end
|
52
|
+
|
53
|
+
if include_last
|
54
|
+
@last = last.floor
|
55
|
+
else
|
56
|
+
@last = last.ceil - 1
|
57
|
+
end
|
58
|
+
|
59
|
+
@timestep = timestep.new_origin(timestep.time_at(@start))
|
60
|
+
@count = @last - @start + 1
|
61
|
+
|
62
|
+
@start_time = @timestep.time_at(@start)
|
63
|
+
@last_time = @timestep.time_at(@last)
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :timestep, :count, :start, :last, :start_time, :last_time
|
67
|
+
|
68
|
+
def_delegators :@timestep, :numeric, :symbol, :interval,
|
69
|
+
:origin, :offset, :calendar, :definition,
|
70
|
+
:parse, :time_at, :index_at, :duration_at, :[]
|
71
|
+
|
72
|
+
|
73
|
+
# FIXME
|
74
|
+
def valid? (*indices)
|
75
|
+
if @count
|
76
|
+
if indices.size == 1
|
77
|
+
index = indices.first
|
78
|
+
if index >= 0 and index < @count
|
79
|
+
return true
|
80
|
+
else
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
else
|
84
|
+
return indices.map{|index| valid?(index) }
|
85
|
+
end
|
86
|
+
else
|
87
|
+
if indices.size == 1
|
88
|
+
return true
|
89
|
+
else
|
90
|
+
return indices.map{|index| true }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns TimeStep object which has origin time determined
|
96
|
+
# by the given `index`.
|
97
|
+
#
|
98
|
+
# @param index [Numeric]
|
99
|
+
#
|
100
|
+
# @return [TimeStep]
|
101
|
+
def shift_origin (index)
|
102
|
+
case with
|
103
|
+
when :index, "index"
|
104
|
+
timestep = @timestep.shift_origin(index)
|
105
|
+
return TimeStep::Range.new(timestep, count: @count)
|
106
|
+
when :duration, "duration", :days, "days"
|
107
|
+
timestep = @timestep.shift_origin(index, with: "duration")
|
108
|
+
return TimeStep::Range.new(timestep, count: @count)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns TimeStep object which has origin time specified
|
113
|
+
# by the given `time`.
|
114
|
+
#
|
115
|
+
# @param time [DateTime]
|
116
|
+
#
|
117
|
+
# @return [TimeStep]
|
118
|
+
def new_origin (time)
|
119
|
+
timestep = @timestep.new_origin(time)
|
120
|
+
return TimeStep::Range.new(timestep, count: @count)
|
121
|
+
end
|
122
|
+
|
123
|
+
include Enumerable
|
124
|
+
|
125
|
+
def each_time (&block)
|
126
|
+
if block
|
127
|
+
@start.upto(@last) do |k|
|
128
|
+
block.call(@timestep.time_at(k))
|
129
|
+
end
|
130
|
+
else
|
131
|
+
return Enumerator.new {|y|
|
132
|
+
@start.upto(@last) do |k|
|
133
|
+
y << @timestep.time_at(k)
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
alias each each_time
|
140
|
+
|
141
|
+
def each_index (&block)
|
142
|
+
(@start..@last).each(&block)
|
143
|
+
end
|
144
|
+
|
145
|
+
def map_index (&block)
|
146
|
+
(@start..@last).map(&block)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|