--[[-- remind(1) compatible formatting. --]]-- local inspect = require "ext.inspect" local pcall = pcall local io = require "io" local iostdout = io.stdout local math = require "math" local mathfloor = math.floor local os = os local osdate = os.date local ostime = os.time local string = require "string" local stringformat = string.format local stringsub = string.sub local stringgsub = string.gsub local table = require "table" local tableconcat = table.concat local common = require "common" local println = common.println local errorln = common.errorln local noiseln = common.noiseln local debugln = common.debugln local trim_whitespace = common.trim_whitespace local unescape_string = common.unescape_string local seconds_of_time = function (t) return t % 60 end local minutes_of_time = function (t) return mathfloor (t / 60) % 60 end local hours_of_time = function (t) return mathfloor (t / (60 * 60)) % 60 end local days_of_time = function (t) return mathfloor (t / (24 * 60 * 60)) end local get_param = function (params, name) local np = #params for i = 1, np do local param = params [i] if param.name == name then return param end end return nil end local get_param_value = function (params, name) local param = get_param (params, name) if param ~= nil then return param.value end return nil end local get_param_value_1 = function (params, name) local val = get_param_value (params, name) if val ~= nil then return val [1] end return nil end local rem_of_vcalendars do local f_sec = 1 local f_min = 60 * f_sec local f_hour = 60 * f_min local f_day = 24 * f_hour local l_month = { [00] = 0 , [01] = 31 , [02] = 30 , [03] = 31 , [04] = 30 , [05] = 31 , [06] = 30 , [07] = 31 , [08] = 31 , [09] = 30 , [10] = 31 , [11] = 30 , [12] = 31 } local time_of_month = function (m, y) local r = 0 for i = 0, m - 1 do local f = l_month [i] if i == 2 and y % 4 == 0 and y % 100 ~= 0 then f = f + 1 end r = r + f * f_day end return r end local time_of_year = function (y) if y % 4 == 0 and y % 100 ~= 0 then end end --[[-- Date format “%Y%M%D” --]]-- local parse_date_value_DATE = function (st, tzid) local Y = tonumber (stringsub (st, 1, 4)) local M = tonumber (stringsub (st, 5, 6)) local D = tonumber (stringsub (st, 7, 8)) return ostime { year = Y, month = M, day = D } end --[[-- Date format 1 date with local time: “%Y%M%DT%h%m%s” Date format 2 date with local time: “%Y%M%DT%h%m%sZ%z” Date format 3 date with local time: “%Y%M%DT%h%m%s” with TZID parm --]]-- local parse_date_value_DATE_TIME = function (st, tzid) if #st < 15 then error (stringformat ("date-time specification [%s] is rubbish", st)) end local Y = tonumber (stringsub (st, 1, 4)) local M = tonumber (stringsub (st, 5, 6)) local D = tonumber (stringsub (st, 7, 8)) assert (stringsub (st, 9, 9) == "T") local h = tonumber (stringsub (st, 10, 11)) local m = tonumber (stringsub (st, 12, 13)) local s = tonumber (stringsub (st, 14, 15)) local t = { year = Y , month = M , day = D , hour = h , min = m , sec = s , isdst = false } if #st == 15 then --- format 3 (with time zone reference) if tzid then error (stringformat ("timezone specification (TZID) for dates \z not implemented, sorry")) end --- format 1 (local) return ostime (t), true end --- format 2 (UTC) assert (stringsub (st, 16, 16) == "Z", stringformat ("rubbish date-time specification %s", st)) return ostime (t), false end --[[-- if params lack a VALUE entry, DATE-TIME is assumed --]]-- local parse_date_value = function (val) local params = val.params local ptzid = get_param_value_1 (params, "TZID") local pdate = get_param_value_1 (params, "VALUE") if pdate == nil or pdate == "DATE-TIME" then return parse_date_value_DATE_TIME (val.value, ptzid) end if pdate == "DATE" then return parse_date_value_DATE (val.value, ptzid), false end return nil end local rem_date = function (d, loctime) if loctime == true then return osdate ("%b %d %Y AT %H:%M", d) end return osdate ("!%b %d %Y AT %H:%M", d) end local rem_duration = function (dt) --[[-- remind(1): Note that duration is specified in hours and minutes. --]]-- local s local ret = "" s = dt % 60 ret = ret .. stringformat ("%0.2d", s) dt = mathfloor (dt / 60) if dt == 0 then return ret end return stringformat ("%0.2d:%s", dt, ret) end local rem_date_duration = function (t0, te, loctime) local dt = te - t0 local s_t0 = rem_date (t0, loctime) local s_dt = rem_duration (dt) return stringformat ("%s DURATION %s", s_t0, s_dt) end local date_of_interval = function (dtstart, dtend) local t0, loctime = parse_date_value (dtstart) local te, _ignore = parse_date_value (dtend) if t0 ~= nil then if te ~= nil then return rem_date_duration (t0, te, loctime) end return rem_date (t0, loctime) end return nil end local string_of_textdata = function (val) local sane = unescape_string (val.value) return trim_whitespace (sane) end local msg_of_string = function (s) return stringgsub (s, "\n", "^M") end local rem_of_vevent = function (ev) local dtstart, dtend local date, msg, msg_desc local vals = ev.value local nvals = #vals for i = 1, nvals do local val = vals [i] if val.kind == "other" then local valname = val.name if valname == "summary" then msg = string_of_textdata (val) elseif valname == "description" then msg_desc = string_of_textdata (val) elseif valname == "dtstart" then dtstart = val elseif valname == "dtend" then dtend = val else debugln ("rem_of_vevent: ignoring value [%s]", valname) end else end end date = date_of_interval (dtstart, dtend) if date == nil then error ("could not derive date from event values") end if msg == nil then error ("could not derive message text from event values") end if msg_desc ~= nil then msg = stringformat ("%s (%s)", msg, msg_desc) end return stringformat ("REM %s MSG %s", date, msg_of_string (msg)) end local rem_of_vcalendar = function (vcal) local acc = { } local vals = vcal.value local nvals = #vals for i = 1, nvals do local val = vals [i] if val.kind ~= "scope" or val.name ~= "VEVENT" then noiseln ("rem_of_vcalendar: ignoring value %d of type [%s/%s]", i, val.kind, val.name) else local ok, res = pcall (rem_of_vevent, val) if ok then acc [#acc + 1] = res else errorln ("rem_of_vcalendar: vevent %d contains invalid entry: %s", i, tostring (res)) end end end return tableconcat (acc, "\n") end rem_of_vcalendars = function (vcals) local value = vcals.value local nvcal = #value local acc = { } for i = 1, nvcal do local vcal = value [i] if vcal.kind ~= "scope" or vcal.name ~= "VCALENDAR" then noiseln ("rem_of_vcalendars: ignoring object %d of type [%s/%s]", i, vcal.kind, vcal.name) else acc [#acc + 1] = rem_of_vcalendar (vcal) end end return tableconcat (acc, "\n") end end --- [rem_of_vcalendars] ------------------------------------------------------------------------------- --- API ------------------------------------------------------------------------------- local output_calendar = function (cal, fh) fh = fh or iostdout local ok, str = pcall (rem_of_vcalendars, cal) if ok then fh:write (str) end end return { output_calendar = output_calendar }