time_interval 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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ *.gem
5
+
6
+ ## TEXTMATE
7
+ *.tmproj
8
+ tmtags
9
+
10
+ ## EMACS
11
+ *~
12
+ \#*
13
+ .\#*
14
+
15
+ ## VIM
16
+ *.swp
17
+
18
+ ## PROJECT::GENERAL
19
+ coverage
20
+ rdoc
21
+ pkg
22
+
23
+ ## PROJECT::SPECIFIC
data/README.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ = time_interval
2
+
3
+ This is a method for dividing up time into differing units of scale, either
4
+ a constant factor such as binary or a more arbitrary arrangement. This is
5
+ useful for many things, such as indexing a series of linear events and
6
+ being able to group them by different levels of temporal granularity.
7
+
8
+ Due to the implementation, the limit on dates supported is:
9
+
10
+ 1970-01-01 00:00:00 UTC - 2038-01-19 03:14:07 UTC
11
+
12
+ Standard UNIX time is represented by the number of seconds since epoch, that
13
+ being the start of January 1, 1970. Positive values indicate points in time
14
+ after this, and negative values indicate prior to it. This is called a
15
+ time_t type offset, based on the UNIX time_t data type.
16
+
17
+ TimeInterval is stored as an unsigned 32-bit number by appropriating the
18
+ bit usually reserved for indicating a negative value and using it to encode
19
+ how large or small the interval defined is.
20
+
21
+ == Example
22
+
23
+ # Defaults to the current time
24
+ interval = TimeInterval.new
25
+
26
+ # Convert to a time grouped by 1<<16 seconds
27
+ encoded = interval.to_i(16)
28
+
29
+ # Decode this time back into a regular time_t offset, but this will
30
+ # be rounded to 1<<16 second granularity.
31
+ decoded = TimeInterval.new(encoded)
32
+
33
+ == Copyright
34
+
35
+ Copyright (c) 2010 Scott Tadman, The Working Group
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "time_interval"
8
+ gem.summary = %Q{Calculates time interval subsets}
9
+ gem.description = %Q{Useful for dividing up linear time into nested intervals}
10
+ gem.email = "github@tadman.ca"
11
+ gem.homepage = "http://github.com/tadman/time_interval"
12
+ gem.authors = %w[ tadman ]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/test_*.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :test => :check_dependencies
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "time_interval #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,107 @@
1
+ class TimeInterval
2
+ # == Constants ============================================================
3
+
4
+ MASK = (0..30).to_a.collect { |i| (1 << 31) - (1 << (30 - i)) }.freeze
5
+
6
+ DEFAULT_SCALE = (0..30).to_a.collect { |i| [ i, MASK[i] ] }.freeze
7
+ DEFAULT_SCALE_NAME = (0..30).to_a.freeze
8
+
9
+ # == Class Methods ========================================================
10
+
11
+ def self.with_definition(definition)
12
+ scale_defn = nil
13
+ scale_name_defn = [ ]
14
+
15
+ case (definition)
16
+ when Array
17
+ case (definition[0])
18
+ when String, Symbol
19
+ definition = [ 1 ] + definition
20
+ end
21
+
22
+ scale_defn = { }
23
+ scale_factor = 1
24
+
25
+ definition.each_with_index do |element, i|
26
+ case (i % 2)
27
+ when 0
28
+ scale_factor *= element
29
+ when 1
30
+ scale_defn[element] = [ MASK[scale_defn.length], scale_factor ]
31
+ scale_name_defn << element
32
+ end
33
+ end
34
+ else
35
+ scale = [ ]
36
+
37
+ 31.times do |i|
38
+ scale_factor = i ** definition
39
+ if (scale_factor < (1 << 31))
40
+ scale_defn.push([ MASK[i], scale_factor ])
41
+ scale_name_defn << i
42
+ end
43
+ end
44
+ end
45
+
46
+ subclass = nil
47
+
48
+ if (scale_defn)
49
+ subclass = Class.new(self)
50
+
51
+ methods = Module.new do
52
+ define_method(:scale) do
53
+ scale_defn
54
+ end
55
+
56
+ define_method(:scale_name) do
57
+ scale_name_defn
58
+ end
59
+ end
60
+
61
+ subclass.send(:extend, methods)
62
+ end
63
+
64
+ subclass or self
65
+ end
66
+
67
+ def self.interval_to_i(value)
68
+ value = value.to_i
69
+
70
+ if (value < 0)
71
+ value = -value
72
+
73
+ index = (0..30).to_a.reverse.detect do |i|
74
+ MASK[i] == value & MASK[i]
75
+ end
76
+
77
+ value = (value ^ MASK[index]) * scale[scale_name[index]][1]
78
+ end
79
+
80
+ value
81
+ end
82
+
83
+ def self.scale
84
+ DEFAULT_SCALE
85
+ end
86
+
87
+ def self.scale_name
88
+ DEFAULT_SCALE_NAME
89
+ end
90
+
91
+ # == Instance Methods =====================================================
92
+
93
+ def initialize(value = nil)
94
+ @time = self.class.interval_to_i(value || Time.now)
95
+ end
96
+
97
+ def to_i(at_scale = nil)
98
+ case (at_scale)
99
+ when nil, 0
100
+ @time
101
+ else
102
+ scale_details = self.class.scale[at_scale]
103
+
104
+ -(scale_details[0] | (@time / scale_details[1]))
105
+ end
106
+ end
107
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'time_interval'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,58 @@
1
+ require 'helper'
2
+
3
+ class TestTimeInterval < Test::Unit::TestCase
4
+ def test_mask_integrity
5
+ assert_equal TimeInterval::MASK.length, TimeInterval::MASK.uniq.length
6
+ end
7
+
8
+ def test_default_state
9
+ interval = TimeInterval.new
10
+
11
+ assert_equal interval.to_i, Time.now.to_i
12
+ end
13
+
14
+ def test_custom_interval
15
+ # Create a custom interval with a simple definition based on the regular
16
+ # calendar.
17
+
18
+ interval_type = TimeInterval.with_definition(
19
+ [ :second, 60, :minute, 60, :hour, 24, :day, 7, :week ]
20
+ )
21
+
22
+ assert_equal 5, interval_type.scale.length
23
+
24
+ # Construct a series of expectations by multiplying out into the
25
+ # required numbre of seconds.
26
+ weeks = 5
27
+ days = 4 + weeks * 7
28
+ hours = 3 + days * 24
29
+ minutes = 2 + hours * 60
30
+ seconds = 1 + minutes * 60
31
+
32
+ # Create an interval with this value
33
+ interval = interval_type.new(seconds)
34
+
35
+ assert interval
36
+
37
+ # When accessed using the default #to_i conversion method, the value
38
+ # returned should be seconds since epoch.
39
+ assert_equal seconds, interval.to_i
40
+
41
+ # Different interval slices can be obtained by passing in the name from
42
+ # the definition.
43
+ assert_equal -(TimeInterval::MASK[0] | seconds), interval.to_i(:second)
44
+ assert_equal -(TimeInterval::MASK[1] | minutes), interval.to_i(:minute)
45
+ assert_equal -(TimeInterval::MASK[2] | hours), interval.to_i(:hour)
46
+ assert_equal -(TimeInterval::MASK[3] | days), interval.to_i(:day)
47
+ assert_equal -(TimeInterval::MASK[4] | weeks), interval.to_i(:week)
48
+
49
+ # These times can be decoded to a value approximately equal to the
50
+ # original, losing granularity where it has been masked out.
51
+
52
+ assert_equal weeks * 7 * 24 * 60 * 60, interval_type.new(interval.to_i(:week)).to_i
53
+ assert_equal days * 24 * 60 * 60, interval_type.new(interval.to_i(:day)).to_i
54
+ assert_equal hours * 60 * 60, interval_type.new(interval.to_i(:hour)).to_i
55
+ assert_equal minutes * 60, interval_type.new(interval.to_i(:minute)).to_i
56
+ assert_equal seconds, interval_type.new(interval.to_i(:second)).to_i
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: time_interval
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - tadman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-24 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Useful for dividing up linear time into nested intervals
22
+ email: github@tadman.ca
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - .document
31
+ - .gitignore
32
+ - README.rdoc
33
+ - Rakefile
34
+ - VERSION
35
+ - lib/time_interval.rb
36
+ - test/helper.rb
37
+ - test/test_time_interval.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/tadman/time_interval
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Calculates time interval subsets
70
+ test_files:
71
+ - test/helper.rb
72
+ - test/test_time_interval.rb