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 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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
@@ -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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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