timespan 0.1.4 → 0.2.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.
@@ -0,0 +1,59 @@
1
+ class Timespan
2
+ class TimeDuration
3
+ include Timespan::Units
4
+
5
+ attr_reader :duration, :reverse
6
+
7
+ def initialize duration, options = {}
8
+ @duration = ::Duration.new(duration)
9
+ @reverse = options[:reverse] || (@duration.total < 0)
10
+ end
11
+
12
+ def total
13
+ reverse ? -(duration.total.abs) : duration.total
14
+ end
15
+
16
+ def self.create_reverse duration
17
+ self.new duration, :reverse => true
18
+ end
19
+ end
20
+
21
+ module Compare
22
+
23
+ include Comparable
24
+
25
+ def time_left time = nil
26
+ time_compare = time || Time.now
27
+ diff = end_time - time_compare
28
+ Timespan::TimeDuration.new(diff)
29
+ end
30
+
31
+ def expired?
32
+ time_left.total <= 0
33
+ end
34
+
35
+ def <=> time
36
+ raise ArgumentError, "Not a valid argument for Timespan comparison, was #{time}" unless valid_compare?(time)
37
+ case time
38
+ when Timespan
39
+ seconds <=> time.seconds
40
+ when Time
41
+ seconds <=> time.to_i
42
+ when Date, DateTime
43
+ time.to_time.to_i
44
+ when Integer
45
+ seconds <=> time
46
+ when ActiveSupport::Duration
47
+ seconds <=> time.to_i
48
+ end
49
+ end
50
+
51
+ def valid_compare? time
52
+ valid_compare_types.any? {|type| time.kind_of? type }
53
+ end
54
+
55
+ def valid_compare_types
56
+ [Timespan, Time, Date, DateTime, Integer]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ require "timespan"
2
+ require "mongoid/fields"
3
+
4
+ # Mongoid serialization support for Timespan type.
5
+ module Mongoid
6
+ module Fields
7
+ class Timespan
8
+ include Mongoid::Fields::Serializable
9
+
10
+ # Deserialize a Timespan given the hash stored by Mongodb
11
+ #
12
+ # @param [Hash] Timespan as hash
13
+ # @return [Timespan] deserialized Timespan
14
+ def deserialize(timespan_hash)
15
+ return if !timespan_hash
16
+ ::Timespan.new(:from => timespan_hash[:from], :to => timespan_hash[:to])
17
+ end
18
+
19
+ # Serialize a Timespan or a Hash (with Timespan units) or a Duration in some form to
20
+ # a BSON serializable type.
21
+ #
22
+ # @param [Timespan, Hash, Integer, String] value
23
+ # @return [Hash] Timespan in seconds
24
+ def serialize(value)
25
+ return if value.blank?
26
+ timespan = ::Timespan.new(value)
27
+ {:from => time_span.start_time, :to => time_span.end_time, :Timespan => time_span.Timespan}
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+ require 'i18n'
2
+
3
+ class Timespan
4
+ class << self
5
+ attr_writer :time_format
6
+
7
+ def time_format
8
+ @time_format ||= "%d %b %Y"
9
+ end
10
+ end
11
+
12
+ module Printer
13
+ # locale-dependent terminology
14
+ # %~s, %~m, %~h, %~d and %~w
15
+ #
16
+ # %td => total days
17
+ # %th => total hours
18
+ # %tm => total minutes
19
+ # %ts => total seconds
20
+ def to_s mode = :full
21
+ meth = "print_#{mode}"
22
+ raise ArgumentError, "Print mode not supported, was: #{mode}" unless respond_to?(meth)
23
+ send(meth)
24
+ end
25
+
26
+ def print_dates
27
+ "#{i18n_t 'from'} #{print :start_time} #{i18n_t 'to'} #{print :end_time}"
28
+ end
29
+
30
+ def print_duration
31
+ print :duration
32
+ end
33
+
34
+ def print_full
35
+ [print_dates, i18n_t('lasting'), print_duration].join(' ')
36
+ end
37
+
38
+ protected
39
+
40
+ def i18n_t label
41
+ I18n.t(label, :scope => :timespan, :default => label.to_s)
42
+ end
43
+
44
+ def print type
45
+ return duration.format(duration_format) if type == :duration
46
+ raise ArgumentError, "Not a valid print type, was: #{type}" unless valid_print_type? type
47
+ send(type).strftime(time_format)
48
+ end
49
+
50
+ def valid_print_type? type
51
+ %w{start_time end_time}.include? type.to_s
52
+ end
53
+
54
+ def time_format
55
+ Timespan.time_format
56
+ end
57
+
58
+ def duration_format
59
+ identifiers = []
60
+ identifiers << 'y' if (duration.years > 0)
61
+ identifiers << 'o' if (duration.months > 0)
62
+ identifiers << 'w' if (duration.weeks > 0)
63
+ identifiers << 'd' if (duration.days > 0)
64
+ identifiers << 'h' if (duration.hours > 0)
65
+ identifiers << 'm' if (duration.minutes > 0)
66
+ identifiers << 's' if (duration.seconds > 0)
67
+
68
+ identifiers.map {|id| "%#{id} %~#{id}" }.join(' ')
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ module Timespan
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ initializer 'Timespan setup' do
5
+ I18n.load_path << Dir[Rails.root.join('config', 'locales', 'timespan', '*.yml')]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ class Timespan
2
+ class DurationParseError < StandardError; end
3
+
4
+ module Span
5
+ attr_reader :duration
6
+
7
+ def duration= duration
8
+ @duration = case duration
9
+ when Duration
10
+ duration
11
+ when Numeric, Hash
12
+ Duration.new duration
13
+ when String
14
+ Duration.new parse_duration(duration)
15
+ else
16
+ raise ArgumentError, "Unsupported duration type: #{duration}"
17
+ end
18
+ unless is_new?
19
+ add_dirty :duration
20
+ refresh!
21
+ calculate!
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def parse_duration text
28
+ spanner_parse text
29
+ rescue Spanner::ParseError => e
30
+ chronic_parse text
31
+ rescue ChronicDuration::DurationParseError => e
32
+ raise Timespan::DurationParseError, "Internal error: neither Spanner or ChronicDuration could parse '#{duration}'"
33
+ end
34
+
35
+ def spanner_parse text
36
+ Spanner.parse(text.gsub /and/, '')
37
+ end
38
+
39
+ def chronic_parse text
40
+ ChronicDuration.parse text
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,92 @@
1
+ class Timespan
2
+ module Units
3
+ def to_milliseconds
4
+ @to_seconds ||= (seconds * 1000.0).round
5
+ end
6
+ alias_method :to_mils, :to_milliseconds
7
+ alias_method :millis, :to_mils
8
+ alias_method :milliseconds, :to_mils
9
+
10
+ def seconds
11
+ @seconds ||= duration.total
12
+ end
13
+
14
+ alias_method :to_secs, :seconds
15
+ alias_method :to_seconds, :seconds
16
+
17
+ def to_minutes
18
+ @to_minutes ||= (to_seconds / 60.0).round
19
+ end
20
+ alias_method :to_m, :to_minutes
21
+ alias_method :to_mins, :to_minutes
22
+ alias_method :minutes, :to_minutes
23
+
24
+ def to_hours
25
+ @to_hours ||= (to_minutes / 60.0).round
26
+ end
27
+ alias_method :to_h, :to_hours
28
+ alias_method :hrs, :to_hours
29
+ alias_method :hours, :to_hours
30
+
31
+ def to_days
32
+ @to_days ||= (to_hours / 24.0).round
33
+ end
34
+ alias_method :to_d, :to_days
35
+ alias_method :days, :to_days
36
+
37
+ def to_weeks
38
+ @to_weeks ||= (to_days / 7.0).round
39
+ end
40
+ alias_method :to_w, :to_weeks
41
+ alias_method :weeks, :to_days
42
+
43
+ def to_months
44
+ @to_months ||= (to_days / 30.0).round
45
+ end
46
+ alias_method :to_mon, :to_months
47
+ alias_method :months, :to_months
48
+
49
+ def to_years
50
+ @to_years ||= (to_days.to_f / 365.25).round
51
+ end
52
+ alias_method :to_y, :to_years
53
+ alias_method :years, :to_years
54
+
55
+ def to_decades
56
+ @to_decades ||= (to_years / 10.0).round
57
+ end
58
+ alias_method :decades, :to_decades
59
+
60
+ def to_centuries
61
+ @to_centuries ||= (to_decades / 10.0).round
62
+ end
63
+ alias_method :centuries, :to_centuries
64
+
65
+ def self.duration_units
66
+ %w{seconds minutes hours days weeks months years}
67
+ end
68
+
69
+ def self.units
70
+ duration_units + %w{decades centuries}
71
+ end
72
+
73
+ def units
74
+ Timespan::Units.units
75
+ end
76
+
77
+ duration_units.each do |unit|
78
+ define_method :"#{unit}=" do |number|
79
+ raise ArgumentError, "Must be a Numeric, was: #{number.inspect}" unless number.kind_of? Numeric
80
+ self.duration = Duration.new(unit.to_sym => number)
81
+ end
82
+ end
83
+
84
+ def decades= number
85
+ self.years = number * 10
86
+ end
87
+
88
+ def centuries= number
89
+ self.decades = number * 10
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Timespan" do
4
+ subject { timespan }
5
+
6
+ context 'From and To with 1 day apart' do
7
+ let(:timespan) { Timespan.new :from => from, :to => to}
8
+
9
+ let(:from) { Chronic.parse("1 day ago") }
10
+ let(:to) { Time.now }
11
+
12
+ describe '.compare == ' do
13
+ specify do
14
+ (subject == 1.day).should be_true
15
+ end
16
+ end
17
+
18
+ describe '.compare < ' do
19
+ specify do
20
+ (subject < 1.day).should be_false
21
+ end
22
+ end
23
+
24
+ describe '.compare > ' do
25
+ specify do
26
+ (subject > 1.day).should be_false
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'From 2 days ago until today' do
32
+ let(:timespan) { Timespan.new :from => "2 days ago", :to => "1 hour ago" }
33
+
34
+ describe '.time_left' do
35
+ it 'should have 0 days left' do
36
+ timespan.time_left.days.should == 0
37
+ end
38
+
39
+ it 'should have 1 hour left' do
40
+ timespan.time_left.hrs.should == -1
41
+ end
42
+ end
43
+
44
+ describe '.expired?' do
45
+ it 'should have 0 days left' do
46
+ timespan.expired?.should be_true
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,20 @@
1
+ da:
2
+ timespan:
3
+ from: fra
4
+ to: til
5
+ lasting: der varer ialt
6
+ ruby_duration:
7
+ second: sekond
8
+ seconds: sekonder
9
+ minute: minut
10
+ minutes: minutter
11
+ hour: time
12
+ hours: timer
13
+ day: dag
14
+ days: dage
15
+ week: uge
16
+ weeks: uges
17
+ month: måned
18
+ months: måneder
19
+ year: år
20
+ years: år
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'i18n'
3
+
4
+ I18n.load_path << File.join(File.dirname(__FILE__), 'locales', 'duration_da.yml')
5
+
6
+ describe Timespan::Span do
7
+ subject { timespan }
8
+
9
+ let(:from) { Chronic.parse("1 day ago") }
10
+ let(:to) { Time.now }
11
+
12
+ before do
13
+ I18n.locale = :da
14
+ end
15
+
16
+ describe 'print in danish - multiple modes' do
17
+ let(:timespan) { Timespan.new :from => from, :to => to }
18
+
19
+ its(:to_s) { should == 'fra 03 May 2012 til 04 May 2012 der varer ialt 1 dag' }
20
+
21
+ specify { subject.to_s(:dates).should == 'fra 03 May 2012 til 04 May 2012' }
22
+ specify { subject.to_s(:duration).should == '1 dag' }
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timespan::Span do
4
+ subject { timespan }
5
+
6
+ let(:from) { Chronic.parse("1 day ago") }
7
+ let(:to) { Time.now }
8
+
9
+ describe 'set seconds to new' do
10
+ let(:timespan) { Timespan.new :from => from, :to => to }
11
+
12
+ before :each do
13
+ @old_timespan = timespan.clone
14
+ @new_timespan = timespan.clone
15
+ @new_timespan.seconds = 120
16
+ end
17
+
18
+ its(:duration) { should be_a Duration }
19
+ specify { subject.send(:dirty).should be_empty }
20
+
21
+ it 'should have diff durations' do
22
+ @old_timespan.duration.should_not == @new_timespan.duration
23
+ end
24
+
25
+ it 'should have diff timespans in minutes' do
26
+ @old_timespan.minutes.should_not == @new_timespan.minutes
27
+ end
28
+ end
29
+
30
+ describe 'set minutes to new' do
31
+ let(:timespan) { Timespan.new :from => from, :to => to }
32
+
33
+ before :each do
34
+ @old_timespan = timespan.clone
35
+ @new_timespan = timespan.clone
36
+ @new_timespan.minutes = 50
37
+ end
38
+
39
+ its(:duration) { should be_a Duration }
40
+ specify { subject.send(:dirty).should be_empty }
41
+
42
+ it 'should have diff durations' do
43
+ @old_timespan.duration.should_not == @new_timespan.duration
44
+ end
45
+
46
+ it 'should have diff timespans in minutes' do
47
+ @old_timespan.minutes.should_not == @new_timespan.minutes
48
+ end
49
+ end
50
+
51
+ describe 'set duration to new' do
52
+ let(:timespan) { Timespan.new :from => from, :to => to }
53
+
54
+ before :each do
55
+ @old_timespan = timespan.clone
56
+ @new_timespan = timespan.clone
57
+ @new_timespan.duration = "7 days"
58
+ end
59
+
60
+ its(:duration) { should be_a Duration }
61
+ specify { subject.send(:dirty).should be_empty }
62
+
63
+ it 'should have diff timespans' do
64
+ @old_timespan.days.should_not == @new_timespan.days
65
+ end
66
+ end
67
+ end