time_scales 0.1.0
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 +15 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +136 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/time_scales.rb +28 -0
- data/lib/time_scales/frame.rb +50 -0
- data/lib/time_scales/frame/base.rb +122 -0
- data/lib/time_scales/frame/null_frame.rb +9 -0
- data/lib/time_scales/frame/part_components.rb +258 -0
- data/lib/time_scales/frame/part_def.rb +45 -0
- data/lib/time_scales/frame/part_defs.rb +53 -0
- data/lib/time_scales/frame/precisions.rb +83 -0
- data/lib/time_scales/frame/scheme_relative_frame.rb +21 -0
- data/lib/time_scales/frame/type_builder.rb +73 -0
- data/lib/time_scales/parts.rb +242 -0
- data/lib/time_scales/time_struct.rb +68 -0
- data/lib/time_scales/units.rb +101 -0
- data/lib/time_scales/version.rb +3 -0
- data/time_scales.gemspec +34 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTIzNjFmNzRjODFmZmE3Y2FmNTc2YTcyZWY1NzQzZGQwZjNkNDAwZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTFhYTU0NmFiOGNjMDYwMWNiZDhmY2Y0M2VjN2QyZGU2NGRhMzgxYw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Yzc5NzE0YWQzNTNjNDc0MTQyYTAwZTRmOWM1ZDk2ZmE3ZDQzOGE4ODJmNjMx
|
10
|
+
ZTY3ODNhNzRhNjZhMjFmMzBjZjRmYTJjOGUyMWRlOWIyM2M5MzY0MWNlNzkw
|
11
|
+
NGE4ZjcwZWMwYjhiZGFiZWFhNmM5ZmU4ZWJhMzc5NTAwYmEzYzA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZjRkOTFiODQ0NmYwZjJjOTY2Mjc0Y2U5OWY4NDlkNjc3NTEzMTE2NjEyMzdh
|
14
|
+
NDY4NGYyMGQwZmZiMTU0YTMzM2IzOTMwNjg5NDNiNDRmMmEzZGZlNGMyYTQx
|
15
|
+
OGNiODBmNzc4YzRmOWY5MWE5NzY0MWRiN2I3NTQ0MmNiMzc0ZTI=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# These development dependencies are temporarily
|
4
|
+
# loaded from here in Gemfile because they have
|
5
|
+
# to come from github, and I can't figure out how
|
6
|
+
# to do that in the gemspec file.
|
7
|
+
%w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
|
8
|
+
gem lib, :git => "git://github.com/rspec/#{lib}.git"
|
9
|
+
end
|
10
|
+
|
11
|
+
# Specify your gem's dependencies in time_scales.gemspec
|
12
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Steve Jorgensen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# TimeScales
|
2
|
+
|
3
|
+
Date/Time representations with specific scopes, units, and precisions.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby gem 'time_scales' ```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install time_scales
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The fuctionality of TimeScales is primarily accessed through
|
22
|
+
frame types and instances of frame types.
|
23
|
+
|
24
|
+
It's probably most useful to start with some examples…
|
25
|
+
|
26
|
+
Example A:
|
27
|
+
|
28
|
+
# Build a frame from part values, and get time value & range.
|
29
|
+
|
30
|
+
frame = TimeScales[year: 2015, quarter: 4, month: 1, day: 21]
|
31
|
+
|
32
|
+
puts frame.begin_time
|
33
|
+
# Output: 2015-10-21 00:00:00 -0700
|
34
|
+
|
35
|
+
puts frame.to_range
|
36
|
+
# Output: 2015-10-21 00:00:00 -0700...2015-10-22 00:00:00 -0700
|
37
|
+
# Note that the range excludes its end value as indicated by
|
38
|
+
# "...".
|
39
|
+
|
40
|
+
Example B:
|
41
|
+
|
42
|
+
# Derive a frame composed of year, day-of-year, and
|
43
|
+
# hour-of-day from a Time value.
|
44
|
+
|
45
|
+
frame_type = TimeScales[:year, :day, :hour]
|
46
|
+
frame = frame_type & Time.new(2011,6,25 , 13,15)
|
47
|
+
|
48
|
+
puts(
|
49
|
+
frame.year_of_scheme,
|
50
|
+
frame.day_of_year,
|
51
|
+
frame.hour_of_day,
|
52
|
+
)
|
53
|
+
# == Output ==
|
54
|
+
# 2011
|
55
|
+
# 176
|
56
|
+
# 13
|
57
|
+
|
58
|
+
Example C:
|
59
|
+
|
60
|
+
# Determine whether a date falls within a particular month
|
61
|
+
# of a particular quarter of its year.
|
62
|
+
|
63
|
+
frame_type = TimeScales[:quarter, :month]
|
64
|
+
some_time = Time.new(2011,10,31 , 12,00)
|
65
|
+
|
66
|
+
puts frame_type & some_time == frame_type.new(3, 1)
|
67
|
+
# Output: false
|
68
|
+
|
69
|
+
puts frame_type & some_time == frame_type.new(4, 1)
|
70
|
+
# Output: true
|
71
|
+
|
72
|
+
A frame type consists of a number of parts, each of which has a
|
73
|
+
scope unit and a subdivision unit. The frame type has an outer
|
74
|
+
scope (the scope of its largest part) and a precision (the
|
75
|
+
subdivision of its smallest part).
|
76
|
+
|
77
|
+
In order to be a valid set of parts for a frame type, it must be
|
78
|
+
possible to form a chain of parts such that the scope unit of a
|
79
|
+
smaller part is the same as the subdivision unit of a larger
|
80
|
+
part. It is possible to specify a part by its subdivision unit
|
81
|
+
if there is any such part that will properly chain to the next
|
82
|
+
larger part. When a unit specifies the largest part for a frame
|
83
|
+
type, then it refers to the unit's default part (e.g. DayOfMonth
|
84
|
+
for Day).
|
85
|
+
|
86
|
+
Of the available units, the Scheme unit is special. Scheme refers
|
87
|
+
to the date/time scheme of the time values that a frame can be
|
88
|
+
built from or converted into. There can never be a unit larger
|
89
|
+
than Scheme, and Scheme can only be the outer scope of a frame
|
90
|
+
type. Also, only a frame instance with a Scheme-scoped type (a
|
91
|
+
specific type) can be converted into a Time value.
|
92
|
+
|
93
|
+
The available units are…
|
94
|
+
* `TimeScales::Units::Scheme` (`:scheme`)
|
95
|
+
* `TimeScales::Units::Year` (`:year`)
|
96
|
+
* `TimeScales::Units::Quarter` (`:quarter`)
|
97
|
+
* `TimeScales::Units::Month` (`:month`)
|
98
|
+
* `TimeScales::Units::Day` (`:day`)
|
99
|
+
* `TimeScales::Units::Hour` (`:hour`)
|
100
|
+
* `TimeScales::Units::Hour` (`:minute`)
|
101
|
+
|
102
|
+
The available frame parts are…
|
103
|
+
* `TimeScales::Parts::YearOfScheme` (`:year_of_scheme`, default for `Year`)
|
104
|
+
* `TimeScales::Parts::QuarterOfyear` (`:quarter_of_year`, default for `Quarter`)
|
105
|
+
* `TimeScales::Parts::MonthOfYear` (`:month_of_year`, default for `Year`)
|
106
|
+
* `TimeScales::Parts::MonthOfQuarter` (`:month_of_quarter`)
|
107
|
+
* `TimeScales::Parts::DayOfMonth` (`:day_of_month`, default for `Day`)
|
108
|
+
* `TimeScales::Parts::DayOfYear` (`:day_of_year`)
|
109
|
+
* `TimeScales::Parts::DayOfQuarter` (`:day_of_quarter`)
|
110
|
+
* `TimeScales::Parts::HourOfDay` (`:hour_of_day`, default for `Hour`)
|
111
|
+
* `TimeScales::Parts::MinuteOfHour` (`:minute_of_hour`, default for `Minute`)
|
112
|
+
|
113
|
+
## Future Plans
|
114
|
+
|
115
|
+
Hopefully, TimeScales will eventually deal with ISO-8601 weeks as
|
116
|
+
well as variations based on different starting day of week.
|
117
|
+
|
118
|
+
## Development
|
119
|
+
|
120
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
121
|
+
run `bin/console` for an interactive prompt that will allow you to
|
122
|
+
experiment.
|
123
|
+
|
124
|
+
To install this gem onto your local machine, run
|
125
|
+
`bundle exec rake install`. To release a new version, update the version
|
126
|
+
number in `version.rb`, and then run `bundle exec rake release` to create a
|
127
|
+
git tag for the version, push git commits and tags, and push the `.gem`
|
128
|
+
file to [rubygems.org](https://rubygems.org).
|
129
|
+
|
130
|
+
## Contributing
|
131
|
+
|
132
|
+
1. Fork it ( https://github.com/[my-github-username]/time_scales/fork )
|
133
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
134
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
135
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
136
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "time_scales"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/time_scales.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require "time_scales/version"
|
3
|
+
require "time_scales/time_struct"
|
4
|
+
require "time_scales/units"
|
5
|
+
require "time_scales/parts"
|
6
|
+
require "time_scales/frame"
|
7
|
+
|
8
|
+
module TimeScales
|
9
|
+
class TimeScalesError < StandardError ; end
|
10
|
+
class NoPartOrUnitForKeyError < TimeScalesError ; end
|
11
|
+
|
12
|
+
def self.[](hash_or_first_part, *args)
|
13
|
+
defines_type =
|
14
|
+
Symbol === hash_or_first_part ||
|
15
|
+
hash_or_first_part.respond_to?(:to_time_scales_part) ||
|
16
|
+
hash_or_first_part.respond_to?(:to_time_scales_unit)
|
17
|
+
if defines_type
|
18
|
+
TimeScales::Frame.type_for( hash_or_first_part, *args )
|
19
|
+
elsif hash_or_first_part.respond_to?( :to_hash ) || hash_or_first_part.respond_to?( :to_h )
|
20
|
+
raise ArgumentError, "Must supply only a hash argument when first argument is a hash" unless args.empty?
|
21
|
+
hash = hash_or_first_part.to_hash if hash_or_first_part.respond_to?( :to_hash )
|
22
|
+
hash = hash_or_first_part.to_h if hash_or_first_part.respond_to?( :to_h )
|
23
|
+
TimeScales::Frame[ hash_or_first_part ]
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Must supply time-part key arguments or a single hash as arguments"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'time_scales/frame/part_def'
|
3
|
+
require 'time_scales/frame/part_defs'
|
4
|
+
require 'time_scales/frame/base'
|
5
|
+
require 'time_scales/frame/null_frame'
|
6
|
+
require 'time_scales/frame/scheme_relative_frame'
|
7
|
+
require 'time_scales/frame/part_components'
|
8
|
+
require 'time_scales/frame/precisions'
|
9
|
+
require 'time_scales/frame/type_builder'
|
10
|
+
|
11
|
+
module TimeScales
|
12
|
+
|
13
|
+
module Frame
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def type_for(*part_keys)
|
17
|
+
return Frame::NullFrame if part_keys.empty?
|
18
|
+
|
19
|
+
part_defs = PartDefs.from_key_value_map( part_keys )
|
20
|
+
type_for_parts( part_defs.parts )
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](frame_parts = {})
|
24
|
+
return Frame::NullFrame.instance if frame_parts.keys.empty?
|
25
|
+
|
26
|
+
part_defs = PartDefs.from_key_value_map( frame_parts )
|
27
|
+
instance_for_part_defs( part_defs )
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def type_for_parts(parts)
|
33
|
+
return Frame::NullFrame if parts.empty?
|
34
|
+
builder = TypeBuilder.new(parts)
|
35
|
+
builder.call
|
36
|
+
end
|
37
|
+
|
38
|
+
def instance_for_part_defs(defs)
|
39
|
+
type = type_for_parts( defs.parts )
|
40
|
+
type.new( *defs.part_values )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class NullFrame < Frame::Base
|
45
|
+
include Singleton
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module TimeScales
|
2
|
+
module Frame
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def parts
|
8
|
+
@parts ||=
|
9
|
+
_parts.
|
10
|
+
sort_by { |part| -part.scale }.
|
11
|
+
freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def outer_scope
|
15
|
+
parts.first.scope
|
16
|
+
end
|
17
|
+
|
18
|
+
def precision
|
19
|
+
parts.last.subdivision
|
20
|
+
end
|
21
|
+
|
22
|
+
def &(time)
|
23
|
+
part_values = parts.map { |part|
|
24
|
+
part & time
|
25
|
+
}
|
26
|
+
new( *part_values )
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def _parts
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(*args)
|
37
|
+
_initialize args
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_time_scales_frame
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def type
|
45
|
+
self.class
|
46
|
+
end
|
47
|
+
|
48
|
+
def parts
|
49
|
+
@parts ||= Hash[
|
50
|
+
self.class.parts.map { |part|
|
51
|
+
[ part.symbol, send(part.symbol) ]
|
52
|
+
}
|
53
|
+
]
|
54
|
+
@parts.dup
|
55
|
+
end
|
56
|
+
|
57
|
+
def outer_scope
|
58
|
+
self.class.outer_scope
|
59
|
+
end
|
60
|
+
|
61
|
+
def precision
|
62
|
+
self.class.precision
|
63
|
+
end
|
64
|
+
|
65
|
+
# Symmetric, hash-key equality.
|
66
|
+
def eql?(other)
|
67
|
+
self.class == other.class &&
|
68
|
+
self._to_a == other._to_a
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
return true if eql?( other )
|
73
|
+
return false unless other.respond_to?( :to_time_scales_frame )
|
74
|
+
other = other.to_time_scales_frame
|
75
|
+
other.outer_scope == outer_scope &&
|
76
|
+
other.precision == precision &&
|
77
|
+
other.begin_time_struct == begin_time_struct
|
78
|
+
end
|
79
|
+
|
80
|
+
def hash
|
81
|
+
@hash ||= self.class.hash ^ _to_a.hash
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_a
|
85
|
+
_to_a.dup
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def _to_a
|
91
|
+
@to_a ||= self.class.parts.map{ |part|
|
92
|
+
send( part.symbol )
|
93
|
+
}.freeze
|
94
|
+
end
|
95
|
+
|
96
|
+
def begin_time_struct
|
97
|
+
@begin_time = begin
|
98
|
+
struct = TimeStruct.new
|
99
|
+
prepare_time_struct struct
|
100
|
+
struct.normalize
|
101
|
+
struct.freeze
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def _initialize(args_array)
|
108
|
+
#stub
|
109
|
+
end
|
110
|
+
|
111
|
+
def ensure_fixnum(value)
|
112
|
+
return value if Fixnum === value
|
113
|
+
raise ArgumentError, "Time part value must be of Fixnum type (a numeric integer)"
|
114
|
+
end
|
115
|
+
|
116
|
+
def prepare_time_struct(struct)
|
117
|
+
# stub
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|