timerizer 0.0.3 → 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.
Files changed (2) hide show
  1. data/lib/timerizer.rb +360 -54
  2. metadata +1 -1
data/lib/timerizer.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  require 'date'
2
2
 
3
- # Represents a relative amount of time. For example, `5 days`, `4 years`, and `5 years, 4 hours, 3 minutes, 2 seconds` are all RelativeTimes.
3
+ # Represents a relative amount of time. For example, '`5 days`', '`4 years`', and '`5 years, 4 hours, 3 minutes, 2 seconds`' are all RelativeTimes.
4
4
  class RelativeTime
5
- # All potential units. Key is the unit name, and the value is its plural form.
6
5
  @@units = {
7
6
  :second => :seconds,
8
7
  :minute => :minutes,
@@ -16,7 +15,6 @@ class RelativeTime
16
15
  :millennium => :millennia
17
16
  }
18
17
 
19
- # Unit values in seconds. If a unit is not present in this hash, it is assumed to be in the {@@in_months} hash.
20
18
  @@in_seconds = {
21
19
  :second => 1,
22
20
  :minute => 60,
@@ -25,7 +23,6 @@ class RelativeTime
25
23
  :week => 604800
26
24
  }
27
25
 
28
- # Unit values in months. If a unit is not present in this hash, it is assumed to be in the {@@in_seconds} hash.
29
26
  @@in_months = {
30
27
  :month => 1,
31
28
  :year => 12,
@@ -40,6 +37,69 @@ class RelativeTime
40
37
  :year => 31556952
41
38
  }
42
39
 
40
+ # Default syntax formats that can be used with #to_s
41
+ # @see #to_s
42
+ @@syntaxes = {
43
+ :micro => {
44
+ :units => {
45
+ :seconds => 's',
46
+ :minutes => 'm',
47
+ :hours => 'h',
48
+ :days => 'd',
49
+ :weeks => 'w',
50
+ :months => 'mn',
51
+ :years => 'y',
52
+ },
53
+ :separator => '',
54
+ :delimiter => ' ',
55
+ :count => 1
56
+ },
57
+ :short => {
58
+ :units => {
59
+ :seconds => 'sec',
60
+ :minutes => 'min',
61
+ :hours => 'hr',
62
+ :days => 'd',
63
+ :weeks => 'wk',
64
+ :months => 'mn',
65
+ :years => 'yr',
66
+ :centuries => 'ct',
67
+ :millenia => 'ml'
68
+ },
69
+ :separator => '',
70
+ :delimiter => ' ',
71
+ :count => 2
72
+ },
73
+ :long => {
74
+ :units => {
75
+ :seconds => ['second', 'seconds'],
76
+ :minutes => ['minute', 'minutes'],
77
+ :hours => ['hour', 'hours'],
78
+ :days => ['day', 'days'],
79
+ :weeks => ['week', 'weeks'],
80
+ :months => ['month', 'months'],
81
+ :years => ['year', 'years'],
82
+ :centuries => ['century', 'centuries'],
83
+ :millennia => ['millenium', 'millenia'],
84
+ }
85
+ }
86
+ }
87
+
88
+ # All potential units. Key is the unit name, and the value is its plural form.
89
+ def self.units
90
+ @@units
91
+ end
92
+
93
+ # Unit values in seconds. If a unit is not present in this hash, it is assumed to be in the {@@in_months} hash.
94
+ def self.units_in_seconds
95
+ @@in_seconds
96
+ end
97
+
98
+ # Unit values in months. If a unit is not present in this hash, it is assumed to be in the {@@in_seconds} hash.
99
+ def self.units_in_months
100
+ @@in_months
101
+ end
102
+
43
103
  # Initialize a new instance of RelativeTime.
44
104
  # @overload new(hash)
45
105
  # @param [Hash] units The base units to initialize with
@@ -47,21 +107,20 @@ class RelativeTime
47
107
  # @option units [Fixnum] :months The number of months
48
108
  # @overload new(count, unit)
49
109
  # @param [Fixnum] count The number of units to initialize with
50
- # @param [Symbol] unit The unit to initialize. See {@@units}
110
+ # @param [Symbol] unit The unit to initialize. See {RelativeTime#units}
51
111
  def initialize(count = 0, unit = :second)
52
- if(count.is_a? Hash)
53
- @seconds = count[:seconds] || 0
54
- @months = count[:months] || 0
55
- return
56
- end
57
-
58
- @seconds = 0
59
- @months = 0
112
+ if count.is_a? Hash
113
+ units = count
114
+ units.default = 0
115
+ @seconds, @months = units.values_at(:seconds, :months)
116
+ else
117
+ @seconds = @months = 0
60
118
 
61
- if(@@in_seconds.has_key?(unit))
62
- @seconds = count * @@in_seconds.fetch(unit)
63
- elsif(@@in_months.has_key?(unit))
64
- @months = count * @@in_months.fetch(unit)
119
+ if @@in_seconds.has_key?(unit)
120
+ @seconds = count * @@in_seconds.fetch(unit)
121
+ elsif @@in_months.has_key?(unit)
122
+ @months = count * @@in_months.fetch(unit)
123
+ end
65
124
  end
66
125
  end
67
126
 
@@ -70,8 +129,11 @@ class RelativeTime
70
129
  # @return [Boolean] True if both RelativeTimes are equal
71
130
  # @note Be weary of rounding; this method compares both RelativeTimes' base units
72
131
  def ==(time)
73
- raise ArgumentError unless time.is_a?(RelativeTime)
74
- return @seconds == time.get(:seconds) && @months == time.get(:months)
132
+ if time.is_a?(RelativeTime)
133
+ @seconds == time.get(:seconds) && @months == time.get(:months)
134
+ else
135
+ false
136
+ end
75
137
  end
76
138
 
77
139
  # Return the number of base units in a RelativeTime.
@@ -79,16 +141,20 @@ class RelativeTime
79
141
  # @return [Fixnum] The requested unit count
80
142
  # @raise [ArgumentError] Unit requested was not :seconds or :months
81
143
  def get(unit)
82
- return @seconds if unit == :seconds
83
- return @months if unit == :months
84
- raise ArgumentError
144
+ if unit == :seconds
145
+ @seconds
146
+ elsif unit == :months
147
+ @months
148
+ else
149
+ raise ArgumentError
150
+ end
85
151
  end
86
152
 
87
153
  # Determines the time between RelativeTime and the given time.
88
154
  # @param [Time] time The initial time.
89
155
  # @return [Time] The difference between the current RelativeTime and the given time
90
156
  # @example 5 hours before January 1st, 2000 at noon
91
- # 5.minutes.before(Time.now(2000, 1, 1, 12, 00, 00))
157
+ # 5.minutes.before(Time.new(2000, 1, 1, 12, 00, 00))
92
158
  # => 2000-01-01 11:55:00 -0800
93
159
  # @see #ago
94
160
  # @see #after
@@ -102,7 +168,7 @@ class RelativeTime
102
168
  new_month += 12
103
169
  new_year -= 1
104
170
  end
105
- if(Date.valid_date?(new_year, new_month, time.day))
171
+ if Date.valid_date?(new_year, new_month, time.day)
106
172
  new_day = time.day
107
173
  else
108
174
  new_day = Date.new(new_year, new_month).days_in_month
@@ -135,7 +201,7 @@ class RelativeTime
135
201
  new_year += 1
136
202
  new_month -= 12
137
203
  end
138
- if(Date.valid_date?(new_year, new_month, time.day))
204
+ if Date.valid_date?(new_year, new_month, time.day)
139
205
  new_day = time.day
140
206
  else
141
207
  new_day = Date.new(new_year, new_month).days_in_month
@@ -161,9 +227,9 @@ class RelativeTime
161
227
  superior_unit = @@units.keys.index(unit) + 1
162
228
 
163
229
  define_method(in_method) do
164
- if(@@in_seconds.has_key?(unit))
230
+ if @@in_seconds.has_key?(unit)
165
231
  @seconds / @@in_seconds[unit]
166
- elsif(@@in_months.has_key?(unit))
232
+ elsif @@in_months.has_key?(unit)
167
233
  @months / @@in_months[unit]
168
234
  end
169
235
  end
@@ -173,7 +239,7 @@ class RelativeTime
173
239
  count_superior = @@units.keys[superior_unit]
174
240
 
175
241
  time = self.send(in_method)
176
- if(@@units.length > superior_unit)
242
+ if @@units.length > superior_unit
177
243
  time -= self.send(in_superior).send(count_superior).send(in_method)
178
244
  end
179
245
  time
@@ -188,14 +254,16 @@ class RelativeTime
188
254
  # @see #average!
189
255
  # @see #unaverage
190
256
  def average
191
- return self unless @seconds > 0
192
-
193
- months = (@seconds / @@average_seconds[:month])
194
- seconds = @seconds - months.months.unaverage.get(:seconds)
195
- RelativeTime.new({
196
- :seconds => seconds,
197
- :months => months + @months
198
- })
257
+ if @seconds > 0
258
+ months = (@seconds / @@average_seconds[:month])
259
+ seconds = @seconds - months.months.unaverage.get(:seconds)
260
+ RelativeTime.new(
261
+ :seconds => seconds,
262
+ :months => months + @months
263
+ )
264
+ else
265
+ self
266
+ end
199
267
  end
200
268
 
201
269
  # Destructively average second-based units to month-based units.
@@ -217,7 +285,7 @@ class RelativeTime
217
285
  def unaverage
218
286
  seconds = @@average_seconds[:month] * @months
219
287
  seconds += @seconds
220
- RelativeTime.new({:seconds => seconds})
288
+ RelativeTime.new(:seconds => seconds)
221
289
  end
222
290
 
223
291
  # Destructively average month-based units to second-based units.
@@ -251,32 +319,252 @@ class RelativeTime
251
319
  })
252
320
  end
253
321
 
322
+ # Converts {RelativeTime} to {WallClock}
323
+ # @return [WallClock] {RelativeTime} as {WallClock}
324
+ # @example
325
+ # (17.hours 30.minutes).to_wall
326
+ # # => 5:30:00 PM
327
+ def to_wall
328
+ raise WallClock::TimeOutOfBoundsError if @months > 0
329
+ WallClock.new(:second => @seconds)
330
+ end
331
+
254
332
  # Convert {RelativeTime} to a human-readable format.
333
+ # @overload to_s(syntax)
334
+ # @param [Symbol] syntax The syntax from @@syntaxes to use
335
+ # @overload to_s(hash)
336
+ # @param [Hash] hash The custom hash to use
337
+ # @option hash [Hash] :units The unit names to use. See @@syntaxes for examples
338
+ # @option hash [Fixnum] :count The maximum number of units to output. `1` would output only the unit of greatest example (such as the hour value in `1.hour 3.minutes 2.seconds`).
339
+ # @option hash [String] :separator The separator to use in between a unit and its value
340
+ # @option hash [String] :delimiter The delimiter to use in between different unit-value pairs
255
341
  # @example
256
342
  # (14.months 49.hours).to_s
257
343
  # => 2 years, 2 months, 3 days, 1 hour
258
- def to_s
259
- times = []
344
+ # (1.day 3.hours 4.minutes).to_s(:short)
345
+ # => 1d 3hr
346
+ # @raise KeyError Symbol argument isn't in @@syntaxes
347
+ # @raise ArgumentError Argument isn't a hash (if not a symbol)
348
+ # @see @@syntaxes
349
+ def to_s(syntax = :long)
350
+ if syntax.is_a? Symbol
351
+ syntax = @@syntaxes.fetch(syntax)
352
+ end
260
353
 
261
- @@units.each do |unit, plural|
262
- time = self.respond_to?(plural) ? self.send(plural) : 0
263
- times << [time, (time != 1) ? plural : unit] if time > 0
354
+ raise ArgumentError unless syntax.is_a? Hash
355
+ times = []
356
+
357
+ if syntax[:count].nil? || syntax[:count] == :all
358
+ syntax[:count] = @@units.count
359
+ end
360
+ units = syntax.fetch(:units)
361
+
362
+ count = 0
363
+ units = Hash[units.to_a.reverse]
364
+ units.each do |unit, (singular, plural)|
365
+ if count < syntax.fetch(:count)
366
+ time = self.respond_to?(unit) ? self.send(unit) : 0
367
+
368
+ if time > 1 && !plural.nil?
369
+ times << [time, plural]
370
+ count += 1
371
+ elsif time > 0
372
+ times << [time, singular]
373
+ count += 1
374
+ end
375
+ end
264
376
  end
265
377
 
266
378
  times.map do |time|
267
- time.join(' ')
268
- end.reverse.join(', ')
379
+ time.join(syntax[:separator] || ' ')
380
+ end.join(syntax[:delimiter] || ', ')
381
+ end
382
+ end
383
+
384
+ # Represents a time, but not a date. '`7:00 PM`' would be an example of a WallClock object
385
+ class WallClock
386
+ # Represents an error where an invalid meridiem was passed to WallClock.
387
+ # @see #new
388
+ class InvalidMeridiemError < ArgumentError; end
389
+ # Represents an error where a time beyond 24 hours was passed to WallClock.
390
+ # @see #new
391
+ class TimeOutOfBoundsError < ArgumentError; end
392
+
393
+ # Initialize a new instance of WallClock
394
+ # @overload new(hash)
395
+ # @param [Hash] units The units to initialize with
396
+ # @option units [Fixnum] :hour The hour to initialize with
397
+ # @option units [Fixnum] :minute The minute to initialize with
398
+ # @option units [Fixnum] :second The second to initialize with
399
+ # @overload new(hour, minute, meridiem)
400
+ # @param [Fixnum] hour The hour to initialize with
401
+ # @param [Fixnum] minute The minute to initialize with
402
+ # @param [Symbol] meridiem The meridiem to initialize with (`:am` or `:pm`)
403
+ # @overload new(hour, minute, second, meridiem)
404
+ # @param [Fixnum] hour The hour to initialize with
405
+ # @param [Fixnum] minute The minute to initialize with
406
+ # @param [Fixnum] second The second to initialize with
407
+ # @param [Symbol] meridiem The meridiem to initialize with (`:am` or `:pm`)
408
+ # @raise InvalidMeridiemError Meridiem is not `:am` or `:pm`
409
+ def initialize(hour = 0, minute = 0, second = 0, meridiem = :am)
410
+ if hour.is_a?(Hash)
411
+ units = hour
412
+
413
+ second = units[:second] || 0
414
+ minute = units[:minute] || 0
415
+ hour = units[:hour] || 0
416
+ else
417
+ if second.is_a?(String) || second.is_a?(Symbol)
418
+ meridiem = second
419
+ second = 0
420
+ end
421
+
422
+ meridiem = meridiem.downcase.to_sym
423
+ if !(meridiem == :am || meridiem == :pm)
424
+ raise InvalidMeridiemError
425
+ elsif meridiem == :pm && hour > 12
426
+ raise TimeOutOfBoundsError, "hour must be <= 12 for PM"
427
+ elsif hour >= 24 || minute >= 60 || second >= 60
428
+ raise TimeOutOfBoundsError
429
+ end
430
+
431
+ hour += 12 if meridiem == :pm
432
+ end
433
+
434
+ @seconds =
435
+ RelativeTime.units_in_seconds.fetch(:hour) * hour +
436
+ RelativeTime.units_in_seconds.fetch(:minute) * minute +
437
+ second
438
+
439
+ if @seconds >= RelativeTime.units_in_seconds.fetch(:day)
440
+ raise TimeOutOfBoundsError
441
+ end
442
+ end
443
+
444
+ # Returns the time of the WallClock on a date
445
+ # @param [Date] date The date to apply the time on
446
+ # @return [Time] The time after the given date
447
+ # @example yesterday at 5:00
448
+ # time = WallClock.new(5, 00, :pm)
449
+ # time.on(Date.yesterday)
450
+ # => 2000-1-1 17:00:00 -0800
451
+ def on(date)
452
+ date.to_date.to_time + @seconds
453
+ end
454
+
455
+ # Comparse two {WallClock}s.
456
+ # @return [Boolean] True if the WallClocks are identical
457
+ def ==(time)
458
+ if time.is_a? WallClock
459
+ self.in_seconds == time.in_seconds
460
+ else
461
+ false
462
+ end
463
+ end
464
+
465
+ # Get the time of the WallClock, in seconds
466
+ # @return [Fixnum] The total time of the WallClock, in seconds
467
+ def in_seconds
468
+ @seconds
469
+ end
470
+
471
+ # Get the time of the WallClock, in minutes
472
+ # @return [Fixnum] The total time of the WallClock, in minutes
473
+ def in_minutes
474
+ @seconds / RelativeTime.units_in_seconds[:minute]
475
+ end
476
+
477
+ # Get the time of the WallClock, in hours
478
+ # @return [Fixnum] The total time of the WallClock, in hours
479
+ def in_hours
480
+ @seconds / RelativeTime.units_in_seconds[:hour]
481
+ end
482
+
483
+ # Get the second of the WallClock.
484
+ # @return [Fixnum] The second component of the WallClock
485
+ def second
486
+ self.to_relative.seconds
487
+ end
488
+
489
+ # Get the minute of the WallClock.
490
+ # @return [Fixnum] The minute component of the WallClock
491
+ def minute
492
+ self.to_relative.minutes
493
+ end
494
+
495
+ # Get the hour of the WallClock.
496
+ # @param [Symbol] system The houring system to use (either `:twelve_hour` or `:twenty_four_hour`; default `:twenty_four_hour`)
497
+ # @return [Fixnum] The hour component of the WallClock
498
+ def hour(system = :twenty_four_hour)
499
+ hour = self.to_relative.hours
500
+ if (system == :twelve_hour) && hour > 12
501
+ hour - 12
502
+ elsif (system == :twenty_four_hour)
503
+ hour
504
+ else
505
+ raise ArgumentError, "system should be :twelve_hour or :twenty_four_hour"
506
+ end
507
+ end
508
+
509
+ # Get the meridiem of the WallClock.
510
+ # @return [Symbol] The meridiem (either `:am` or `:pm`)
511
+ def meridiem
512
+ if self.hour > 12
513
+ :pm
514
+ else
515
+ :am
516
+ end
517
+ end
518
+
519
+ # Converts self to {WallClock}
520
+ # @see Time#to_wall
521
+ def to_wall
522
+ self
523
+ end
524
+
525
+ # Converts {WallClock} to {RelativeTime}
526
+ # @return [RelativeTime] {WallClock} as {RelativeTime}
527
+ # @example
528
+ # time = WallClock.new(5, 30, :pm)
529
+ # time.to_relative
530
+ # => 5 hours, 30 minutes
531
+ def to_relative
532
+ @seconds.seconds
533
+ end
534
+
535
+ # Convert {WallClock} to a human-readable format.
536
+ # @param [Symbol] system The hour system to use (`:twelve_hour` or `:twenty_four_hour`; default `:twelve_hour`)
537
+ # @example
538
+ # time = WallClock.new(5, 37, :pm)
539
+ # time.to_s
540
+ # => "5:37:00 PM"
541
+ # time.to_s(:twenty_four_hour)
542
+ # => "17:37:00"
543
+ # @raise ArgumentError Argument isn't a proper system
544
+ def to_s(system = :twelve_hour)
545
+ if(system == :twelve_hour)
546
+ meridiem = self.meridiem.to_s.upcase
547
+ "#{self.hour(system)}:#{self.minute}:#{self.second} #{meridiem}"
548
+ elsif(system == :twenty_four_hour)
549
+ "#{self.hour(system)}:#{self.minute}:#{self.second}"
550
+ else
551
+ raise ArgumentError, "system should be :twelve_hour or :twenty_four_hour"
552
+ end
269
553
  end
270
554
  end
271
555
 
272
556
  # {Time} class monkeywrenched with {RelativeTime} support.
273
557
  class Time
274
- class TimeIsInThePastException < Exception; end
275
- class TimeIsInTheFutureException < Exception; end
558
+ # Represents an error where two times were expected to be in the future, but were in the past.
559
+ # @see #until
560
+ class TimeIsInThePastError < ArgumentError; end
561
+ # Represents an error where two times were expected to be in the past, but were in the future.
562
+ # @see #since
563
+ class TimeIsInTheFutureError < ArgumentError; end
276
564
 
277
565
  add = instance_method(:+)
278
566
  define_method(:+) do |time|
279
- if(time.class == RelativeTime)
567
+ if time.is_a? RelativeTime
280
568
  time.after(self)
281
569
  else
282
570
  add.bind(self).(time)
@@ -285,7 +573,7 @@ class Time
285
573
 
286
574
  subtract = instance_method(:-)
287
575
  define_method(:-) do |time|
288
- if(time.class == RelativeTime)
576
+ if time.is_a? RelativeTime
289
577
  time.before(self)
290
578
  else
291
579
  subtract.bind(self).(time)
@@ -302,13 +590,13 @@ class Time
302
590
  # @see Time#since
303
591
  # @see Time#between
304
592
  def self.until(time)
305
- raise TimeIsInThePastException if Time.now > time.to_time
593
+ raise TimeIsInThePastError if Time.now > time.to_time
306
594
 
307
595
  Time.between(Time.now, time)
308
596
  end
309
597
 
310
598
  # Calculates the time since a given time
311
- # @param [Time] since The time to calculate since now
599
+ # @param [Time] time The time to calculate since now
312
600
  # @return [RelativeTime] The time since the provided time
313
601
  # @raise[TimeIsInTheFutureException] The provided time is in the future
314
602
  # @example
@@ -317,7 +605,7 @@ class Time
317
605
  # @see Time#since
318
606
  # @see Time#between
319
607
  def self.since(time)
320
- raise TimeIsInTheFutureException if time.to_time > Time.now
608
+ raise TimeIsInTheFutureError if time.to_time > Time.now
321
609
 
322
610
  Time.between(Time.now, time)
323
611
  end
@@ -352,6 +640,17 @@ class Time
352
640
  def to_time
353
641
  self
354
642
  end
643
+
644
+ # Converts {Time} to {WallClock}
645
+ # @return [WallClock] {Time} as {WallClock}
646
+ # @example
647
+ # time = Time.now.to_wall
648
+ # Date.tomorrow.at(time)
649
+ # => 2000-1-2 13:13:27 -0800
650
+ # # "Same time tomorrow?"
651
+ def to_wall
652
+ WallClock.new(self.hour, self.min, self.sec)
653
+ end
355
654
  end
356
655
 
357
656
  # {Date} class monkeywrenched with {RelativeTime} helpers.
@@ -377,6 +676,14 @@ class Date
377
676
  self
378
677
  end
379
678
 
679
+ # Apply a time to a date
680
+ # @example yesterday at 5:00
681
+ # Date.yesterday.at(WallClock.new(5, 00, :pm))
682
+ # => 2000-1-1 17:00:00 -0800
683
+ def at(time)
684
+ time.to_wall.on(self)
685
+ end
686
+
380
687
  # Return tomorrow as {Date}.
381
688
  # @see Date#yesterday
382
689
  def self.tomorrow
@@ -394,10 +701,9 @@ end
394
701
  # @example
395
702
  # 5.minutes
396
703
  # => 5 minutes
397
- # @see RelativeTime @@units
704
+ # @see {RelativeTime#units}
398
705
  class Fixnum
399
- units = RelativeTime.class_variable_get(:@@units)
400
- units.each do |unit, plural|
706
+ RelativeTime.units.each do |unit, plural|
401
707
  define_method(unit) do |added_time = RelativeTime.new|
402
708
  time = RelativeTime.new(self, unit)
403
709
  time + added_time
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timerizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: