class Fugit::Duration

def ==(o)

def ==(o)
  o.is_a?(Fugit::Duration) && o.h == @h
end

def _to_s(key)

def _to_s(key)
  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 add(a)

def add(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 add #{a.class} instance to a Fugit::Duration")
  end
end

def add_duration(d)

def add_duration(d)
  params = d.h.inject(@h.dup) { |h, (k, v)| h[k] = (h[k] || 0) + v; h }
  self.class.allocate.init(nil, {}, params)
end

def add_numeric(n)

def add_numeric(n)
  h = @h.dup
  h[:sec] = (h[:sec] || 0) + n.to_i
  self.class.allocate.init(nil,{}, h)
end

def add_to_time(t)

def add_to_time(t)
  t = ::EtOrbi.make_time(t)
  INFLA_KEYS.each do |k, a|
    v = @h[k]; next unless v
    t = t + v * a[:s]
  end
  NON_INFLA_KEYS.each do |k, a|
    v = @h[k]; next unless v
    at = [ t.year, t.month, t.day, t.hour, t.min, t.sec ]
    at[a[:x]] += v
    if at[1] > 12
      n, m = at[1] / 12, at[1] % 12
      at[0], at[1] = at[0] + n, m
    elsif at[1] < 1
      n, m = -at[1] / 12, -at[1] % 12
      at[0], at[1] = at[0] - n, m
    end
    t = ::EtOrbi.make_time(at, t.zone)
  end
  t
end

def common_rewrite_dur(t)

def common_rewrite_dur(t)
  t
    .subgather(nil)
    .inject({}) { |h, tt|
      v = tt.string; v = v.index('.') ? v.to_f : v.to_i
        # drops ending ("y", "m", ...) by itself
      h[tt.name] = (h[tt.name] || 0) + v
      h }
end

def deflate(options={})

def deflate(options={})
  id = inflate
  h = id.h.dup
  s = h.delete(:sec) || 0
  keys = INFLA_KEYS
  mon = options[:month]
  yea = options[:year]
  keys = keys.dup if mon || yea
  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
  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 do_parse(s, opts={})

def do_parse(s, opts={})
  parse(s, opts) ||
  fail(ArgumentError.new("not a duration #{s.inspect}"))
end

def drop_seconds


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

def hash

def hash
  @h.hash
end

def inflate

def inflate
  params =
    @h.inject({ sec: 0 }) { |h, (k, v)|
      a = KEYS[k]
      if a[:I]
        h[:sec] += (v * a[:s])
      else
        h[k] = v
      end
      h
    }
  self.class.allocate.init(@original, {}, params)
end

def init(original, options, h)

def init(original, options, h)
  @original = original
  @options = options
  if options[:literal]
    @h = h
  else
    @h = h.reject { |k, v| v == 0 }
    @h[:sec] = 0 if @h.empty?
  end
  self
end

def new(s)

def new(s)
  parse(s)
end

def next_time(from=::EtOrbi::EoTime.now)

def next_time(from=::EtOrbi::EoTime.now)
  add(from)
end

def opposite

def opposite
  params = @h.inject({}) { |h, (k, v)| h[k] = -v; h }
  self.class.allocate.init(nil, {}, params)
end

def parse(s, opts={})

def parse(s, opts={})
  return s if s.is_a?(self)
  original = s
  s = "#{s}s" if s.is_a?(Numeric)
  return nil unless s.is_a?(String)
  s = s.strip
riginal, 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
  h ? self.allocate.send(:init, original, opts, h) : nil
end

def subtract(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 subtract #{a.class} instance to a Fugit::Duration")
  end
end

def to_h; h; end


For now, let's alias to #h
def to_h; h; end

def to_iso_s(o); do_parse(o).deflate.to_iso_s; end

def to_iso_s(o); do_parse(o).deflate.to_iso_s; end

def to_iso_s

def to_iso_s
  t = false
  s = StringIO.new
  s << 'P'
  KEYS.each_with_index do |(k, a), i|
    v = @h[k]; next unless v
    if i > 3 && t == false
      t = true
      s << 'T'
    end
    s << v.to_s; s << a[:i]
  end
  s.string
end

def to_long_s(o, opts={}); do_parse(o).deflate.to_long_s(opts); end

def to_long_s(o, opts={}); do_parse(o).deflate.to_long_s(opts); end

def to_long_s(opts={})

def to_long_s(opts={})
  s = StringIO.new
  adn = [ false, 'no' ].include?(opts[:oxford]) ? ' and ' : ', and '
  a = @h.to_a
  while kv = a.shift
    k, v = kv
    aa = KEYS[k]
    s << v.to_i
    s << ' '; s << aa[:l]; s << 's' if v > 1
    s << (a.size == 1 ? adn : ', ') if a.size > 0
  end
  s.string
end

def to_plain_s(o); do_parse(o).deflate.to_plain_s; end

def to_plain_s(o); do_parse(o).deflate.to_plain_s; end

def to_plain_s; _to_s(:a); end

def to_plain_s; _to_s(:a); end

def to_rufus_h

def to_rufus_h
  KEYS.inject({}) { |h, (ks, kh)| v = @h[ks]; h[kh[:r].to_sym] = v if v; h }
end

def to_rufus_s; _to_s(:r); end

def to_rufus_s; _to_s(:r); end

def to_sec


365 days, ...
Warning: this is an "approximation", months are 30 days and years are
def to_sec
  KEYS.inject(0) { |s, (k, a)| v = @h[k]; next s unless v; s += v * a[:s] }
end