############################################################################ # Copyright 2009 Benjamin Kellermann # # # # This file is part of dudle. # # # # Dudle is free software: you can redistribute it and/or modify it under # # the terms of the GNU Affero General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # Dudle is distributed in the hope that it will be useful, but WITHOUT ANY # # WARRANTY; without even the implied warranty of MERCHANTABILITY or # # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # # License for more details. # # # # You should have received a copy of the GNU Affero General Public License # # along with dudle. If not, see . # ############################################################################ # BUGFIX for Time.parse, which handles the zone indeterministically class << Time alias_method :old_parse, :parse def Time.parse(date, now=self.now) Time.old_parse("2009-10-25 00:30") Time.old_parse(date) end end class TimePollHead class TimeString attr_reader :date, :time def initialize(date,time) @date = date.class == Date ? date : Date.parse(date) if time =~ /^\d[\d]?:\d[\d]?$/ begin #TODO: what to do with 24:00 ??? # if time == "24:00" # @date += 1 # time = "00:00" # end @time = Time.parse("#{@date} #{time}") rescue ArgumentError @time = time end else @time = time end end def TimeString.from_s(string) date = string.scan(/^(\d\d\d\d-\d\d-\d\d).*$/).flatten[0] time = string.scan(/^\d\d\d\d-\d\d-\d\d (.*)$/).flatten[0] TimeString.new(date,time) end def TimeString.now TimeString.new(Date.today,Time.now) end include Comparable def <=>(other) if self.date == other.date if self.time.class == String && other.time.class == String || self.time.class == Time && other.time.class == Time self.time <=> other.time elsif self.time.class == NilClass && other.time.class == NilClass 0 else self.time.class == String ? 1 : -1 end else self.date <=> other.date end end def to_s if @time "#{@date} #{time_to_s}" else @date.to_s end end def inspect "TS: date: #{@date} time: #{@time ? time_to_s : "nil"}" end def time_to_s if @time.class == Time return time.strftime("%H:%M") else return @time end end end def initialize @data = [] end def col_size @data.size end # returns a sorted array of all columns # column should be the internal representation # column.to_s should deliver humanreadable form def columns @data.sort.each.collect{|day| day.to_s} end def concrete_times h = {} @data.each{|ds| h[ds.time_to_s] = true } h.keys.compact.sort{|a,b| TimeString.new(Date.today,a) <=> TimeString.new(Date.today,b) } end def date_included?(date) ret = false @data.each{|ds| ret = ret || ds.date == date } ret end # column is in human readable form # returns true if deletion sucessfull def delete_column(column) col = TimeString.from_s(column) if col.time ret = @data.delete(TimeString.from_s(column)) != nil @data << TimeString.new(col.date,nil) unless date_included?(col.date) return ret else deldata = [] @data.each{|ts| deldata << ts if ts.date == col.date } deldata.each{|ts| @data.delete(ts) } return !deldata.empty? end end def parsecolumntitle(title) if $cgi.include?("add_remove_column_day") parsed_date = YAML::load(Time.parse("#{$cgi["add_remove_column_month"]}-#{$cgi["add_remove_column_day"]} #{title}").to_yaml) else earlytime = @head.keys.collect{|t|t.strftime("%H:%M")}.sort[0] parsed_date = YAML::load(Time.parse("#{$cgi["add_remove_column_month"]}-#{title} #{earlytime}").to_yaml) end parsed_date end # returns parsed title def edit_column(column, newtitle, cgi) delete_column(column) if column != "" parsed_date = TimeString.new(newtitle, cgi["columntime"] != "" ? cgi["columntime"] : nil) @data << parsed_date @data.uniq! parsed_date.to_s end # returns a sorted array, containing the big units and how often each small is in the big one # small and big must be formated for strftime # ex: head_count("%Y-%m") returns an array like [["2009-03",2],["2009-04",3]] # if notime = true, the time field is stripped out before counting def head_count(elem, notime) data = @data.collect{|day| day.date} data.uniq! if notime ret = Hash.new(0) data.each{|day| ret[day.strftime(elem)] += 1 } ret.sort end def to_html(scols,config = false,activecolumn = nil) ret = "" head_count("%Y-%m",false).each{|title,count| year, month = title.split("-").collect{|e| e.to_i} ret += "#{Date::ABBR_MONTHNAMES[month]} #{year}\n" } ret += "" head_count("%Y-%m-%d",false).each{|title,count| ret += "#{Date.parse(title).strftime('%a, %d')}\n" } def sortsymb(scols,col) scols.include?(col) ? SORT : NOSORT end ret += "Name #{sortsymb(scols,"name")}" @data.sort.each{|date| ret += "#{date.time_to_s} #{sortsymb(scols,date.to_s)}\n" } ret += "" + _("Last Edit") + " #{sortsymb(scols,"timestamp")}\n\n" ret end def edit_column_htmlform(activecolumn, revision) if $cgi.include?("add_remove_column_month") if $cgi.params["add_remove_column_month"].size == 1 startdate = Date.parse("#{$cgi["add_remove_column_month"]}-1") else olddate = $cgi.params["add_remove_column_month"][1] case $cgi["add_remove_column_month"] when CGI.unescapeHTML(MONTHBACK) startdate = Date.parse("#{olddate}-1")-1 when CGI.unescapeHTML(MONTHFORWARD) startdate = Date.parse("#{olddate}-1")+31 else exit end startdate = Date.parse("#{startdate.year}-#{startdate.month}-1") end else startdate = Date.parse("#{Date.today.year}-#{Date.today.month}-1") end hintstr = _("Click on the dates to add or remove columns.") ret = <
#{hintstr}
END def navi val,curmonth,revision return <
END end ret += navi(MONTHBACK,startdate,revision) ret += "
" ret += navi(MONTHFORWARD,startdate,revision) ret += "\n" 7.times{|i| ret += "" } ret += "\n" ((startdate.wday+7-1)%7).times{ ret += "" } d = startdate while true do klasse = "notchosen" varname = "new_columnname" klasse = "disabled" if d < Date.today if date_included?(d) klasse = "chosen" varname = "deletecolumn" end ret += < TD d = d.next break if d.month != startdate.month ret += "\n" if d.wday == 1 end ret += <
#{startdate.strftime('%b %Y')}
#{Date::ABBR_DAYNAMES[(i+1)%7]}
END ########################### # starting hour input ########################### ret += "" if col_size > 0 optstr = _("Optional:") hintstr = _("Enter a concrete value as start time.") ret += < #{optstr}
#{hintstr} END ret += "" head_count("%Y-%m",true).each{|title,count| year,month = title.split("-").collect{|e| e.to_i} ret += "\n" } ret += "" head_count("%Y-%m-%d",true).each{|title,count| ret += "\n" } ret += "" days = @data.sort.collect{|date| date.date }.uniq chosenstr = { "chosen" => _("chosen"), "notchosen" => _("notchosen"), "disabled" => _("past") } times = [] (9..20).each{|i| times << "#{i.to_s.rjust(2,"0")}:00"} times << concrete_times times.flatten.compact.uniq.sort.each{|time| ret +="\n" days.each{|day| timestamp = TimeString.new(day,time) klasse = "notchosen" klasse = "disabled" if timestamp < TimeString.now klasse = "chosen" if @data.include?(timestamp) ret += <
END if klasse == "chosen" ret += "" else ret += "" if @data.include?(TimeString.new(day,nil)) ret += "" end end ret += <
END } ret += "
\n" } ret += "" days.each{|d| ret += <
END if @data.include?(TimeString.new(d,nil)) ret += "" end addstr = _("Add") hintstr = _("e.g.: 09:30, morning, afternoon") ret += <
END } ret += "
#{Date::ABBR_MONTHNAMES[month]} #{year}
" + _("Time") + "#{Date.parse(title).strftime('%a, %d')}
#{time}
" end ret += "" ret end end