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