Skip to content

Instantly share code, notes, and snippets.

@jirutka
Last active December 1, 2015 17:36
Show Gist options
  • Save jirutka/f6975ad886c6778cd785 to your computer and use it in GitHub Desktop.
Save jirutka/f6975ad886c6778cd785 to your computer and use it in GitHub Desktop.
Quick & dirty script for conversion of CSV export from Toggle to a readable textual table. It also deduplicates entries and groups entries without a description or duration less than 0.5 hours under one entry “Others”.
#!/usr/bin/env ruby
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
require 'csv'
require 'time'
csv_file = ARGV[0]
unless csv_file
$stderr.puts <<EOF
Quick & dirty script for conversion of CSV export from Toggle to a readable
textual table. It also deduplicates entries and groups entries without
a description or duration less than 0.5 hours under one entry "Others".
Usage: #{$0} <csv-file>
EOF
exit 1
end
Entry = Struct.new(:desc, :duration)
def parse_duration(str)
time = Time.parse(str)
(time.hour * 3600 + time.min * 60 + time.sec).to_f / 3600
end
def format_duration(hours)
"#{hours.round(1)} h".gsub('.', ',').rjust(6)
end
others_predicate = -> (entry) do
entry.desc.nil? || entry.desc == '' || entry.duration < 0.5
end
timesheet = CSV.read(csv_file, headers: true).map do |row|
Entry.new(row['Description'].strip, parse_duration(row['Duration']))
end
# Deduplicate
timesheet = timesheet
.group_by(&:desc)
.map { |desc, ary| Entry.new(desc, ary.map(&:duration).reduce(:+)) }
# Sum duration of entries for which others_predicate return true,
other_hours = timesheet
.select(&others_predicate)
.map(&:duration)
.reduce(:+)
# remove them,
timesheet = timesheet.reject(&others_predicate)
# and add as single entry "Others" to the end of the timesheet.
if other_hours > 0
timesheet.push(Entry.new('Others', other_hours))
end
total_hours = timesheet.map(&:duration).reduce(:+)
longest_desc = timesheet.map(&:desc).map(&:length).max
table = timesheet.map do |entry|
"#{entry.desc.ljust(longest_desc)} #{format_duration(entry.duration)}"
end
table << '-' * (longest_desc + 8)
table << "#{'TOTAL'.ljust(longest_desc)} #{format_duration(total_hours)}"
puts table
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment