lib/fugit/duration.rb in fugit-1.0.0 vs lib/fugit/duration.rb in fugit-1.1.0
- old
+ new
@@ -1,11 +1,11 @@
module Fugit
class Duration
- attr_reader :original, :h
+ attr_reader :original, :h, :options
def self.new(s)
parse(s)
end
@@ -14,54 +14,61 @@
return s if s.is_a?(self)
original = s
- s = s.to_s if s.is_a?(Numeric)
+ s = "#{s}s" if s.is_a?(Numeric)
return nil unless s.is_a?(String)
s = s.strip
- s = s + 's' if s.match(/\A-?(\d*\.)?\d+\z/)
-#p [ original, s ]; Raabro.pp(Parser.parse(s, debug: 3))
+#p [ original, s ]; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
h =
if opts[:iso]
IsoParser.parse(opts[:stricter] ? s : s.upcase)
elsif opts[:plain]
Parser.parse(s)
else
Parser.parse(s) || IsoParser.parse(opts[:stricter] ? s : s.upcase)
end
+#p h
- h ? self.allocate.send(:init, original, h) : nil
+ h ? self.allocate.send(:init, original, opts, h) : nil
end
def self.do_parse(s, opts={})
parse(s, opts) || fail(ArgumentError.new("not a duration #{s.inspect}"))
end
KEYS = {
- yea: { a: 'Y', i: 'Y', s: 365 * 24 * 3600, x: 0, l: 'year' },
- mon: { a: 'M', i: 'M', s: 30 * 24 * 3600, x: 1, l: 'month' },
- wee: { a: 'W', i: 'W', s: 7 * 24 * 3600, I: true, l: 'week' },
- day: { a: 'D', i: 'D', s: 24 * 3600, I: true, l: 'day' },
- hou: { a: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
- min: { a: 'm', i: 'M', s: 60, I: true, l: 'minute' },
- sec: { a: 's', i: 'S', s: 1, I: true, l: 'second' },
+ yea: { a: 'Y', r: 'y', i: 'Y', s: 365 * 24 * 3600, x: 0, l: 'year' },
+ mon: { a: 'M', r: 'M', i: 'M', s: 30 * 24 * 3600, x: 1, l: 'month' },
+ wee: { a: 'W', r: 'w', i: 'W', s: 7 * 24 * 3600, I: true, l: 'week' },
+ day: { a: 'D', r: 'd', i: 'D', s: 24 * 3600, I: true, l: 'day' },
+ hou: { a: 'h', r: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
+ min: { a: 'm', r: 'm', i: 'M', s: 60, I: true, l: 'minute' },
+ sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' },
}
INFLA_KEYS, NON_INFLA_KEYS =
KEYS.partition { |k, v| v[:I] }
- def to_plain_s
+ def _to_s(key)
- KEYS.inject(StringIO.new) { |s, (k, a)|
- v = @h[k]; next s unless v; s << v.to_s; s << a[:a]
- }.string
- end
+ KEYS.inject([ StringIO.new, '+' ]) { |(s, sign), (k, a)|
+ v = @h[k]
+ next [ s, sign ] unless v
+ sign1 = v < 0 ? '-' : '+'
+ s << (sign1 != sign ? sign1 : '') << v.abs.to_s << a[key]
+ [ s, sign1 ]
+ }[0].string
+ end; protected :_to_s
+ def to_plain_s; _to_s(:a); end
+ def to_rufus_s; _to_s(:r); end
+
def to_iso_s
t = false
s = StringIO.new
@@ -100,10 +107,19 @@
def to_plain_s(o); do_parse(o).deflate.to_plain_s; end
def to_iso_s(o); do_parse(o).deflate.to_iso_s; end
def to_long_s(o, opts={}); do_parse(o).deflate.to_long_s(opts); end
end
+ # For now, let's alias to #h
+ #
+ def to_h; h; end
+
+ def to_rufus_h
+
+ KEYS.inject({}) { |h, (ks, kh)| v = @h[ks]; h[kh[:r].to_sym] = v if v; h }
+ end
+
# Warning: this is an "approximation", months are 30 days and years are
# 365 days, ...
#
def to_sec
@@ -121,53 +137,75 @@
h[k] = v
end
h
}
- self.class.allocate.init(@original, h)
+ self.class.allocate.init(@original, {}, h)
end
- def deflate
+ # Round float seconds to 9 decimals when deflating
+ #
+ SECOND_ROUND = 9
+ def deflate(options={})
+
id = inflate
h = id.h.dup
s = h.delete(:sec) || 0
- INFLA_KEYS.each do |k, v|
+ keys = INFLA_KEYS
- n = s / v[:s]; next if n == 0
- m = s % v[:s]
+ mon = options[:month]
+ yea = options[:year]
+ keys = keys.dup if mon || yea
- h[k] = (h[k] || 0) + n
- s = m
+ if mon
+ mon = 30 if mon == true
+ mon = "#{mon}d" if mon.is_a?(Integer)
+ keys.unshift([ :mon, { s: Fugit::Duration.parse(mon).to_sec } ])
end
+ if yea
+ yea = 365 if yea == true
+ yea = "#{yea}d" if yea.is_a?(Integer)
+ keys.unshift([ :yea, { s: Fugit::Duration.parse(yea).to_sec } ])
+ end
- self.class.allocate.init(@original, h)
+ keys[0..-2].each do |k, v|
+
+ vs = v[:s]; next if s < vs
+
+ h[k] = (h[k] || 0) + s.to_i / vs
+ s = s % vs
+ end
+
+ h[:sec] = s.is_a?(Integer) ? s : s.round(SECOND_ROUND)
+
+ self.class.allocate.init(@original, {}, h)
end
def opposite
h = @h.inject({}) { |h, (k, v)| h[k] = -v; h }
- self.class.allocate.init(nil, h)
+ self.class.allocate.init(nil, {}, h)
end
alias -@ opposite
def add_numeric(n)
h = @h.dup
h[:sec] = (h[:sec] || 0) + n.to_i
- self.class.allocate.init(nil, h)
+ self.class.allocate.init(nil,{}, h)
end
def add_duration(d)
h = d.h.inject(@h.dup) { |h, (k, v)| h[k] = (h[k] || 0) + v; h }
- self.class.allocate.init(nil, h)
+ self.class.allocate.init(nil, {}, h)
end
def add_to_time(t)
t = ::EtOrbi.make_time(t)
@@ -211,22 +249,22 @@
"cannot add #{a.class} instance to a Fugit::Duration")
end
end
alias + add
- def substract(a)
+ def subtract(a)
case a
when Numeric then add_numeric(-a)
when Fugit::Duration then add_duration(-a)
when String then add_duration(-self.class.parse(a))
when ::Time, ::EtOrbi::EoTime then add_to_time(a)
else fail ArgumentError.new(
- "cannot substract #{a.class} instance to a Fugit::Duration")
+ "cannot subtract #{a.class} instance to a Fugit::Duration")
end
end
- alias - substract
+ alias - subtract
def ==(o)
o.is_a?(Fugit::Duration) && o.h == @h
end
@@ -240,18 +278,34 @@
def next_time(from=::EtOrbi::EoTime.now)
add(from)
end
+ # Returns a copy of this duration, omitting its seconds.
+ #
+ def drop_seconds
+
+ h = @h.dup
+ h.delete(:sec)
+ h[:min] = 0 if h.empty?
+
+ self.class.allocate.init(nil, { literal: true }, h)
+ end
+
protected
- def init(original, h)
+ def init(original, options, h)
@original = original
+ @options = options
- @h = h.reject { |k, v| v == 0 }
- # which copies h btw
+ if options[:literal]
+ @h = h
+ else
+ @h = h.reject { |k, v| v == 0 }
+ @h[:sec] = 0 if @h.empty?
+ end
self
end
def self.common_rewrite_dur(t)
@@ -270,26 +324,50 @@
# piece parsers bottom to top
def sep(i); rex(nil, i, /([ \t,]+|and)*/i); end
- def yea(i); rex(:yea, i, /-?\d+ *y(ears?)?/i); end
- def mon(i); rex(:mon, i, /-?\d+ *(M|months?)/); end
- def wee(i); rex(:wee, i, /-?\d+ *(weeks?|w)/i); end
- def day(i); rex(:day, i, /-?\d+ *(days?|d)/i); end
- def hou(i); rex(:hou, i, /-?\d+ *(hours?|h)/i); end
- def min(i); rex(:min, i, /-?\d+ *(mins?|minutes?|m)/); end
+ def yea(i); rex(:yea, i, /(\d+\.\d*|(\d*\.)?\d+) *y(ears?)?/i); end
+ def mon(i); rex(:mon, i, /(\d+\.\d*|(\d*\.)?\d+) *(M|months?)/); end
+ def wee(i); rex(:wee, i, /(\d+\.\d*|(\d*\.)?\d+) *(weeks?|w)/i); end
+ def day(i); rex(:day, i, /(\d+\.\d*|(\d*\.)?\d+) *(days?|d)/i); end
+ def hou(i); rex(:hou, i, /(\d+\.\d*|(\d*\.)?\d+) *(hours?|h)/i); end
+ def min(i); rex(:min, i, /(\d+\.\d*|(\d*\.)?\d+) *(mins?|minutes?|m)/); end
- def sec(i); rex(:sec, i, /-?((\d*\.)?\d+) *(secs?|seconds?|s)/i); end
- # always last!
+ def sec(i); rex(:sec, i, /(\d+\.\d*|(\d*\.)?\d+) *(secs?|seconds?|s)/i); end
+ def sek(i); rex(:sec, i, /(\d+\.\d*|\.\d+|\d+)$/); end
- def elt(i); alt(nil, i, :yea, :mon, :wee, :day, :hou, :min, :sec); end
+ def elt(i); alt(nil, i, :yea, :mon, :wee, :day, :hou, :min, :sec, :sek); end
+ def sign(i); rex(:sign, i, /[-+]?/); end
- def dur(i); jseq(:dur, i, :elt, :sep); end
+ def sdur(i); seq(:sdur, i, :sign, '?', :elt, '+'); end
+ def dur(i); jseq(:dur, i, :sdur, :sep); end
+
# rewrite parsed tree
- def rewrite_dur(t); Fugit::Duration.common_rewrite_dur(t); end
+ def merge(h0, h1)
+
+ sign = h1.delete(:sign) || 1
+
+ h1.inject(h0) { |h, (k, v)| h.merge(k => (h[k] || 0) + sign * v) }
+ end
+
+ def rewrite_sdur(t)
+
+ h = Fugit::Duration.common_rewrite_dur(t)
+
+ sign = t.sublookup(:sign)
+ sign = (sign && sign.string == '-') ? -1 : 1
+
+ h.merge(sign: sign)
+ end
+
+ def rewrite_dur(t)
+
+#Raabro.pp(t, colours: true)
+ t.children.inject({}) { |h, ct| merge(h, ct.name ? rewrite(ct) : {}) }
+ end
end
module IsoParser include Raabro
# piece parsers bottom to top