Print a forecast of upcoming events from iCal files
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 141 lines 3.9 kB view raw
1#!/usr/bin/env ruby 2# 3# ical_forecast.rb 4# print upcoming events from iCal files 5# 6# Copyright (c) 2010, 2014, 2017 joshua stein <jcs@jcs.org> 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions 10# are met: 11# 12# 1. Redistributions of source code must retain the above copyright 13# notice, this list of conditions and the following disclaimer. 14# 2. Redistributions in binary form must reproduce the above copyright 15# notice, this list of conditions and the following disclaimer in the 16# documentation and/or other materials provided with the distribution. 17# 3. The name of the author may not be used to endorse or promote products 18# derived from this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 21# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30# 31 32require "ri_cal" 33require "tzinfo" 34require "date" 35 36FORECAST_SPAN = (60 * 60 * 24 * 7) 37 38BOLD = "\e[1;1m" 39UNBOLD = "\e[0;0m" 40GRAY = "\e[38;5;239m" 41LIGHTGRAY = "\e[38;5;242m" 42RESET = "\e[0;0m" 43 44upcoming = [] 45 46if !ARGV.any? || ARGV[0] == "-h" 47 puts "usage: #{$0} <ical file...>" 48 exit 1 49end 50 51ARGV.each do |file| 52 RiCal.parse(File.open(file, "rb")).first.events.each do |ev| 53 ev.occurrences(:count => 10).each do |oc| 54 begin 55 start = oc.dtstart.to_time.getlocal 56 finish = oc.dtend.to_time.getlocal 57 rescue RiCal::InvalidTimezoneIdentifier 58 next 59 end 60 61 if finish < Time.now || start > (Time.now + FORECAST_SPAN) 62 next 63 end 64 65 upcoming.push oc 66 end 67 end 68end 69 70upcoming.sort_by{|u| u.dtstart.to_time.getlocal }.reverse.each do |ev| 71 start = ev.dtstart.to_time.getlocal 72 finish = ev.dtend.to_time.getlocal 73 74 weeks = 0 75 days = (start.to_date - Date.today).to_i 76 if days >= 7 77 weeks = (days.to_f / 7.0).floor 78 days = days - (weeks * 7) 79 end 80 81 out = "" 82 if weeks == 0 && days == 0 83 out << BOLD 84 elsif weeks <= 1 85 out << GRAY 86 elsif weeks > 1 87 out << LIGHTGRAY 88 end 89 90 out << ev.summary << " " 91 92 if weeks > 0 93 out << "in #{weeks} week#{weeks == 1 ? "" : "s"}" 94 95 if days > 0 96 out << ", #{days} day#{days == 1 ? "" : "s"}" 97 end 98 else 99 if days < -1 100 out << "#{days.abs} day#{days == -1 ? "" : "s"} ago" 101 elsif days == -1 102 out << "yesterday" 103 elsif days == 0 104 out << "today" 105 elsif days == 1 106 out << "tomorrow" 107 else 108 out << "in #{days} day#{days == 1 ? "" : "s"}" 109 end 110 end 111 112 all_day = (start != finish && start.hour == 0 && finish.hour == 0 && 113 (finish.to_i - start.to_i == (60 * 60 * 24))) 114 115 if !all_day 116 out << " at #{start.strftime("%H:%M")}" 117 118 if start.to_date == Time.now.to_date 119 mins = ((start - Time.now) / 60).floor 120 if start < Time.now 121 out << " (#{mins} minute#{mins == 1 ? "" : "s"} ago)" 122 else 123 out << " (in #{mins} minute#{mins == 1 ? "" : "s"})" 124 end 125 end 126 end 127 128 if weeks == 0 && days == 0 129 out << UNBOLD 130 end 131 132 out << " (" << start.strftime("%a #{start.day} %b").downcase 133 if !all_day && (start.to_date != finish.to_date) 134 out << " to " << finish.strftime("%a #{finish.day} %b").downcase 135 end 136 out << ")" 137 138 out << RESET 139 140 puts out 141end