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 +5 -0
- data/.gitignore +23 -0
- data/README.rdoc +35 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/time_interval.rb +107 -0
- data/test/helper.rb +10 -0
- data/test/test_time_interval.rb +58 -0
- metadata +72 -0
data/.document
ADDED
data/.gitignore
ADDED
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,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
|