timesteps 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -25
- data/lib/timesteps.rb +4 -3
- data/lib/timesteps/{parse_timestamp.rb → datetime_parse_timestamp.rb} +6 -3
- data/lib/timesteps/datetimelike.rb +6 -6
- data/lib/timesteps/{format.rb → datetimelike_format.rb} +0 -0
- data/lib/timesteps/timeperiod.rb +202 -0
- data/lib/timesteps/timestep.rb +107 -101
- data/lib/timesteps/{calendar.rb → timestep_calendar.rb} +5 -0
- data/lib/timesteps/timestep_converter.rb +1 -1
- data/lib/timesteps/timestep_pair.rb +2 -2
- data/lib/timesteps/timestep_query.rb +49 -0
- data/spec/timestep_spec.rb +20 -20
- data/spec/timesteppair_spec.rb +2 -2
- data/timesteps.gemspec +1 -1
- metadata +7 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0feffe90d4bd2fdd8c478624bc78b67e3a7ab1c5905e236767c7734ba8c6b098
         | 
| 4 | 
            +
              data.tar.gz: 4e924fa6b145f2b7cb06836d408b05e8bb34e8133797a3b7702ba29033af4400
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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  | 
| 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  | 
| 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  | 
| 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.  | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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  | 
| 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", " | 
| 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 | 
| 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  | 
| 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/ | 
| 8 | 
            -
            require "timesteps/format"
         | 
| 8 | 
            +
            require "timesteps/datetime_parse_timestamp"
         | 
| 9 9 | 
             
            require "timesteps/timestep_datetime_ext"
         | 
| 10 | 
            -
            require "timesteps/ | 
| 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 " | 
| 41 | 
            +
                  raise "date-time string '#{spec}' doesn't match with the given format '#{format}'" unless hash
         | 
| 43 42 | 
             
                else
         | 
| 44 | 
            -
                   | 
| 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 | 
| 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  | 
| 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 " | 
| 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 | 
            +
             | 
    
        data/lib/timesteps/timestep.rb
    CHANGED
    
    | @@ -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 | 
| 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 | 
            -
                 | 
| 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 | 
            -
                     | 
| 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 | 
            -
                   | 
| 66 | 
            -
                  parse_interval( | 
| 67 | 
            -
                  @origin = @calendar.parse( | 
| 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 " | 
| 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 : | 
| 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 = ( | 
| 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"   =>  | 
| 197 | 
            -
                  " | 
| 198 | 
            -
                  " | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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|  | 
| 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( | 
| 335 | 
            +
                return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
         | 
| 380 336 | 
             
              end
         | 
| 381 337 |  | 
| 382 | 
            -
              def  | 
| 383 | 
            -
                time = @origin +  | 
| 384 | 
            -
                return TimeStep.new( | 
| 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( | 
| 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 " | 
| 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 " | 
| 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,  | 
| 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) | 
| 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 | 
            -
                 | 
| 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
         | 
| @@ -26,7 +26,7 @@ class TimeStep::Pair | |
| 26 26 | 
             
                when TimeStep
         | 
| 27 27 | 
             
                  @from = from
         | 
| 28 28 | 
             
                else
         | 
| 29 | 
            -
                  raise " | 
| 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 " | 
| 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 | 
            +
             | 
    
        data/spec/timestep_spec.rb
    CHANGED
    
    | @@ -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( | 
| 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( | 
| 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# | 
| 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. | 
| 351 | 
            -
                  is_asserted_by { ts. | 
| 352 | 
            -
                  is_asserted_by { ts. | 
| 353 | 
            -
                  is_asserted_by { ts. | 
| 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 ' | 
| 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. | 
| 366 | 
            -
                  is_asserted_by { ts. | 
| 367 | 
            -
                  is_asserted_by { ts. | 
| 368 | 
            -
                  is_asserted_by { ts. | 
| 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. | 
| 381 | 
            -
                  is_asserted_by { ts. | 
| 382 | 
            -
                  is_asserted_by { ts. | 
| 383 | 
            -
                  is_asserted_by { ts. | 
| 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. | 
| 396 | 
            -
                  is_asserted_by { ts. | 
| 397 | 
            -
                  is_asserted_by { ts. | 
| 398 | 
            -
                  is_asserted_by { ts. | 
| 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 |  | 
    
        data/spec/timesteppair_spec.rb
    CHANGED
    
    | @@ -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( | 
| 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( | 
| 80 | 
            +
                expect { pair.forward(1.5) }.to raise_error(ArgumentError)
         | 
| 81 81 |  | 
| 82 82 | 
             
              end
         | 
| 83 83 |  | 
    
        data/timesteps.gemspec
    CHANGED
    
    
    
        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. | 
| 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- | 
| 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/ | 
| 35 | 
            +
            - lib/timesteps/datetimelike_format.rb
         | 
| 36 36 | 
             
            - lib/timesteps/grads.rb
         | 
| 37 | 
            -
            - lib/timesteps/ | 
| 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
         |