Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/awk -f
- # Best viewed with tabs every 4 characters
- # Copyright (C) 2011 Charles
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program 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 General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
- # Acknowledgements and thanks to Kjell-Inge Gustafsson of kigkonsult for
- # some algorithms inspired by iCalcreator class v2.6 PHP code at
- # http://svn.oscc.org.my/taskmanager/branches/2.0/app/vendors/iCalcreator.class.php
- # Acknowledgements and thanks to Concordia University for date calculation
- # theory at http://alcor.concordia.ca/~gpkatch/gdate-method.html and sample
- # awk code at http://alcor.concordia.ca/~gpkatch/gdate-awk.html
- # Name: filter_orage_ics_for_gnokii.awk
- # Purpose:
- # Filters an Orage export .ics file in preparation for gnokii to write to a
- # Nokia Series 40 mobile phone:
- # * Converts recurring VEVENTs to a series of single VEVENTs.
- # * Removes VEVENTs outside the time range -1 to +2 months.
- # Usage: -h option for help (runs the usage function)
- # Environment:
- # Developed and tested on Slackware64 13.1 using
- # * Xfce Orage 4.6.1
- # * GNU Awk 3.1.8
- # * gnokii from git snapshot 5may11
- # * Nokia 5130c-2 (RM-495 with 07.97 firmware, USB 2.0 connection)
- # Support: please contact Charles via the gnokii users mailing list:
- # http://lists.nongnu.org/mailman/listinfo/gnokii-users
- # History:
- # 12may11 Version 0.0. Charles
- # * Limited functionality, proof of concept.
- # 17may11 Version 0.1. Charles
- # * Command line:
- # -d option to set new Debug_level
- # -h option to print help using new usage function
- # -V option to print version
- # * Support for BYDAY as used by Orage (except YEARLY which is
- # incomprehensible or broken)
- # * Startup message including this script's version number
- # 23may11 Version 0.2. Charles
- # * Changed tabs to spaces and applied max 80 line length
- # * Changed RFC 2445 references to RFC 5545
- # * Command line:
- # -a option to make all alarms audio
- # -v option to make all alarms visual
- # TODO: in approx descending priority
- # * Command line:
- # - Logging level option - D, W, I, E
- # - Log file name option
- # - Output time range options
- # - Input and output file options
- # * Output VEVENTs with an alarm time in the output time range.
- # * Use time zone information?
- # * Service TODOs and JOURNALs as well as VEVENTs?
- # Programmers' notes:
- # * The input file format is a subset of "RFC 5545, Internet Calendaring and
- # Scheduling Core Object Specification (iCalendar)" as implemented by the
- # Xfce calendar application, Orage 4.6.1. The text of RFC 5545 is at
- # http://tools.ietf.org/html/rfc5545
- # * This script is also intended as a prototype for C code so it:
- # - Does not use any awk pattern-actions; everything is done by the BEGIN
- # action.
- # - Lists function local variables on a separate line from arguments.
- # * Variable name components:
- # - date DATE as defined in RFC 5545
- # - doe Day of epoch beginnning on 1 March of Gregorian calendar
- # projected backwards to year 1.
- # - time TIME as defined in RFC 5545
- # * Global variables (distinguished by an initial upper-case letter):
- # - Audio_alarm 0 for no coercion, 1 for coercion
- # - Byday_list BYDAY list
- # - Bymonth_list BYMONTH list
- # - Bymonthday_list BYMONTHDAY list
- # - Bysetpos_list BYSETPOS list
- # - Byyearday_list BYYEARDAY list
- # - Count COUNT value
- # - Debug_level 0 for no debugging. Usage message gives further detail.
- # - Dtend DTEND value
- # - Dtend_date
- # - Dtend_line Line of VEVENT in which DTEND is given
- # - Dtend_time
- # - Dtstart DTSTART value
- # - Dtstart_date
- # - Dtstart_line Line of VEVENT in which DTSTART is given
- # - Dtstart_time
- # - Dtstart_time_in_days DTSTART's time component as a float number of days
- # - Duration DURATION value
- # - Duration_days DURATION as a float number of days
- # - Freq FREQ value
- # - Interval INTERVAL value
- # - Lines The lines of a VEVENT
- # - Month_day_counts Array of 31,28,31,30,31,30,31,31,30,31,30,31
- # - My_name Last component of path by which this script was called
- # - N_lines The number of lines in Lines
- # - Optarg An option's argument
- # - Opterr Function get_opt error control; set to 0 to suppress error messages
- # - Optind Index of the first non-option argument in ARGV
- # - Optopt An option's letter
- # - Output_date_range_end_date
- # - Output_date_range_end_doe
- # - Output_date_range_start_date
- # - Output_date_range_start_doe
- # - Recurrence 1 if the VEVENT has an RRULE otherwise 0
- # - Until UNTIL value
- # - Until_date
- # - Version This script's version
- # - Visual_alarm 0 for no coercion, 1 for coercion
- # - Weekdays Array of SU,MO,TU,WE,TH,FR,SA
- # - Wkst WKST value
- # * Global variables for setter's use only (distinguished by an initial _):
- # - _opti Set by the getopt() function
- # * Function call tree:
- # BEGIN
- # |
- # +-- initialise
- # | |
- # | +-- getopt
- # | |
- # | +-- usage
- # |
- # +-- get_begin_vevent (normal exit)
- # |
- # +-- get_vevent
- # | |
- # | +-- parse_dtend
- # | |
- # | +-- parse_dtstart
- # | |
- # | +-- parse_duration
- # | |
- # | +-- parse_rrule
- # |
- # +-- do_vevent
- # | |
- # | +-- write_vevent
- # |
- # +-- do_vevents
- # |
- # +-- get_instance_start_dates
- # | |
- # | +-- get_instance_start_dates_for_month_byday
- # | |
- # | +-- get_instance_start_dates_for_week_byday
- # |
- # +-- modify_dtstart_date
- # |
- # +-- modify_dtend_date
- # |
- # +-- write_vevent
- #
- # Utility functions
- #
- # convert_date_to_doe, convert_doe_to_date, convert_duration_to_days,
- # convert_time_to_days, date_math_add_months, date_math_add_weeks,
- # date_math_add_years, get_month_end_date, get_month_start_date,
- # get_week_start_doe, in_output_range_by_date, in_output_range_by_doe,
- # leap_year, msg, my_getline, parse dt_time, parse_dt, parse_dt_date
- # Function definitions in alphabetical order. Execution begins after the
- # last function definition (search for BEGIN).
- function convert_date_to_doe ( date,
- y, m, d )
- {
- y = substr( date, 1, 4 )
- m = substr( date, 5, 2 )
- d = substr( date, 7, 2 )
- # Convert to calendar with March as first month
- m = ( m + 9 ) % 12
- y = y - int( m / 10 )
- return y * 365 + int( y / 4 ) - int( y / 100 ) + int( y / 400 ) \
- + int( 0.5 + m * 30.6 ) + d - 1
- }
- function convert_doe_to_date ( doe,
- d, day_of_year, m, m_intermediate, y, y_start_doe )
- {
- # Calculate approximate year and its first day of epoch number
- y = int( ( doe + 1.4780 ) / 365.2425 )
- y_start_doe = convert_date_to_doe( y "0301" )
- # Calculate the day of year, adjusting the approximate year if necessary
- if ( doe >= y_start_doe )
- {
- day_of_year = doe - y_start_doe
- }
- else
- {
- y -= 1
- day_of_year = doe - convert_date_to_doe( y "0301" )
- }
- # Calculate final year, month and day of month
- m_intermediate = int( .017 + day_of_year / 30.6 )
- m = ( m_intermediate + 2 ) % 12 + 1
- y += int( ( m_intermediate + 2) / 12 )
- d = day_of_year - int( m_intermediate * 30.6 + 0.5) + 1
- return sprintf("%d%02d%02d", y, m, d)
- }
- function convert_doe_to_weekday ( doe,
- d, date, first_of_month_doy, m, m_adjusted, my_return, y, y_adjusted, \
- y_adjustment )
- {
- # strftime cannot be used because it is locale-dependent
- # The algorithm is Zeller's congruence
- date = convert_doe_to_date( doe )
- y = substr( date, 1, 4 )
- m = substr( date, 5, 2 )
- d = substr( date, 7 )
- # Adjust the year and month to a 1st March based year
- y_adjustment = int( ( 14 - m ) / 12 )
- y_adjusted = y - y_adjustment
- m_adjusted = m + 12 * y_adjustment - 2
- # Calculate the first day of the month as the day of a 1st March based year
- first_of_month_doy = y_adjusted \
- + int( y_adjusted / 4 ) - int( y_adjusted / 100 ) \
- + int( y_adjusted / 400) + int( ( 31 * m_adjusted ) / 12 )
- my_return = Weekdays[ ( first_of_month_doy + d ) % 7 + 1 ]
- msg( "D100", \
- "convert_doe_to_weekday: date: " date ", my_return: " my_return )
- return my_return
- }
- function convert_duration_to_days ( duration,
- buf, idx )
- {
- # The DURATION value is assumed to be of format [<n>H][<n>M][<n>S]
- # subject to at least one of the terms being present.
- # This was observed as Orage's subset of RFC 5545's DURATION.
- buf = 0
- if ( ( idx = index( duration, "H" ) ) > 1 )
- {
- buf = substr( duration, 1, idx -1 ) * 60 * 60
- duration = substr( duration, idx + 1 )
- }
- if ( ( idx = index( duration, "M" ) ) > 1 )
- {
- buf += ( substr( duration, 1, idx - 1 ) ) * 60
- duration = substr( duration, idx + 1 )
- }
- if ( ( idx = index( duration, "S" ) ) > 1 )
- {
- buf += ( substr( duration, 1, idx - 1 ) )
- }
- return buf / ( 24 * 60 * 60 )
- }
- function convert_time_to_days ( time,
- h, m, s )
- {
- # A TIME value has format HHMMSS[Z]
- h = substr( time, 1, 2 )
- m = substr( time, 3, 2 )
- s = substr( time, 5, 2 )
- return ( ( ( ( h * 60 ) + m ) * 60 ) + s ) / ( 24 * 60 * 60 )
- }
- function date_math_add_months ( date, increment,
- my_return, m, y )
- {
- msg( "D100", \
- "date_math_add_months: started. date: " date ", increment: " increment )
- m = substr( date, 5, 2 ) + increment
- y = substr( date, 1, 4 ) + int( m / 12 )
- m = m % 12
- d = substr( date, 7 )
- if ( length( y ) > 4 )
- msg( "E", "date_math_add_months: year > 9999 generated while" \
- " processing VEVENT before input record " NR )
- if ( y < 1 )
- msg( "E", "date_math_add_months: year < 1 generated while" \
- " processing VEVENT before input record " NR )
- if ( m == "02" && d == "29" && ! ( y % 4 == 0 && y % 100 != 0 ) \
- || ( y % 400 == 0 ) \
- )
- return 0
- my_return = sprintf( "%0.4d%0.2d%0.2d", y, m, d )
- msg( "D100", "date_math_add_months: returning " my_return )
- return my_return
- }
- function date_math_add_weeks ( date, increment,
- doe, my_return )
- {
- msg( "D100", "date_math_add_weeks: started. date: " date ", increment: " \
- increment )
- doe = convert_date_to_doe( date ) + ( 7 * increment )
- my_return = convert_doe_to_date( doe )
- msg( "D100", "date_math_add_weeks: returning " my_return )
- return my_return
- }
- function date_math_add_years ( date, increment,
- my_return, y )
- {
- msg( "D100", "date_math_add_years: started. date: " date ", increment: " increment )
- y = substr( date, 1, 4 ) + increment
- if ( length( y ) > 4 )
- msg( "E", "date_math_add_years: year > 9999 generated while" \
- " processing data before input record " NR )
- if ( y < 1 )
- msg( "E", "date_math_add_years: year < 1 generated while" \
- " processing data before input record " NR )
- my_return = y substr( date, 5 )
- msg( "D100", "date_math_add_years: returning " my_return )
- return my_return
- }
- function do_vevent ( \
- end_doe, start_doe )
- {
- start_doe = convert_date_to_doe( Dtstart_date )
- if ( Dtend_date != "" )
- {
- end_doe = convert_date_to_doe( Dtend_date )
- }
- else
- { # Input had DURATION instead of DTEND
- end_doe = int( start_doe + Dtstart_time_in_days + Duration_in_days )
- }
- msg( "D50", "do_vevent:" \
- "\n start_doe: " start_doe \
- "\n end_doe: " end_doe \
- )
- if ( in_output_range_by_doe( start_doe, end_doe ) ) write_vevent( )
- }
- function do_vevents ( \
- array, instance_end_date, instance_start_date, instance_start_dates,
- n_dates, start_to_end_offset \
- )
- {
- msg( "D10", "do_vevents: started" )
- # If input had DTEND, calculate DTSTART/DTEND offset in days
- if ( Dtend_date != "" )
- start_to_end_offset = convert_date_to_doe( Dtend_date ) \
- - convert_date_to_doe( Dtstart_date )
- # Get the start date of events in the series that are in the date range
- instance_start_dates = get_instance_start_dates( )
- # For each event in the recurring series ...
- n_dates = split( instance_start_dates, array, " " )
- msg( "D10", "do_vevents: n_dates: " n_dates )
- for ( i = 1; i <= n_dates; i++ )
- {
- instance_start_date = array[ i ]
- msg( "D20", "do_vevents: instance_start_date: " instance_start_date )
- modify_dtstart_date( instance_start_date )
- if ( Dtend_date != "" )
- {
- instance_end_date = convert_doe_to_date( \
- convert_date_to_doe( instance_start_date ) \
- + start_to_end_offset \
- )
- modify_dtend_date( instance_end_date )
- }
- write_vevent( )
- }
- }
- function get_begin_vevent ()
- {
- for ( ; ; )
- {
- my_getline()
- if ( $0 == "BEGIN:VEVENT" ) break
- if ( $0 == "END:VCALENDAR" ) { print $0; exit 0 }
- print $0
- }
- }
- function get_instance_start_dates ( \
- byday, bydays, days_spanned, dtstart_doe, i, instance_end_date,
- instance_end_doe, instance_start_date, instance_start_doe, j, j_max,
- my_return, until_doe, weekday \
- )
- {
- msg( "D40", "get_instance_start_dates: started" )
- # From RFC 5545:
- # If multiple BYxxx rule parts are specified then, after evaluating the
- # specified FREQ and INTERVAL rule parts, the BYxxx rule parts are
- # applied to the current set of evaluated occurrences in the following
- # order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR,
- # BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated.
- # From inspecting Orage's GUI, source code and sample export .ics files:
- # * RRULEs:
- # - FREQ is given and is one of DAILY, WEEKLY, MONTHLY or YEARLY
- # - One of UNTIL or COUNT may be given
- # - INTERVAL may not be given in which case it is defaulted to 1
- # - BYDAY may be given
- # - WKST may not be given in which case it is defaulted to MON
- # * One and only one DTSTART value is given
- # * One of DTEND or DURATION is given
- my_return = ""
- # Calculate some days-of-epoch
- if ( Until_date == "" ) until_doe = 0
- else until_doe = convert_date_to_doe( Until_date )
- instance_start_doe = convert_date_to_doe( Dtstart_date )
- if ( Freq == "DAILY" )
- {
- # Calculate first VEVENT days
- if ( Dtend_date != "" )
- {
- instance_end_date = Dtend_date
- instance_end_doe = convert_date_to_doe( instance_end_date )
- }
- else
- { # Input had DURATION instead of DTEND
- instance_end_doe = int( instance_start_doe + Dtstart_time_in_days \
- + Duration_in_days )
- }
- msg( "D50", "get_instance_start_dates, DAILY: " \
- "\n Dtstart_date: " Dtstart_date \
- "\n instance_start_doe: " instance_start_doe \
- "\n instance_end_doe: " instance_end_doe \
- )
- # For each event in the series up to the end of the output time range
- for ( i = 1; Count == 0 || i <= Count; ) # "Count == 0" is for UNTIL
- {
- if ( instance_start_doe > Output_date_range_end_doe ) break
- if ( until_doe && instance_start_doe > until_doe ) break
- skip = 0
- if ( Byday_list != "" )
- { # For DAILY FREQs, this is a list of day names
- weekday = convert_doe_to_weekday( instance_start_doe )
- if ( index( Byday_list, weekday ) == 0 ) skip = 1
- }
- if ( ! skip &&
- instance_start_doe <= Output_date_range_end_doe &&
- instance_end_doe >= Output_date_range_start_doe \
- ) my_return = my_return " " convert_doe_to_date( instance_start_doe )
- # Prepare for next iteration
- i++
- instance_start_doe += Interval
- instance_end_doe += Interval
- }
- msg( "D50", "get_instance_start_dates: returning: " my_return )
- return my_return
- }
- else
- {
- # Calculate first VEVENT days and number of days spanned
- instance_start_date = Dtstart_date
- if ( Dtend_date != "" )
- {
- instance_end_date = Dtend_date
- }
- else
- { # Input had DURATION instead of DTEND
- instance_end_doe = int( instance_start_doe + Dtstart_time_in_days \
- + Duration_in_days )
- instance_end_date = convert_doe_to_date( instance_end_doe )
- }
- days_spanned = \
- convert_date_to_doe( end_date ) - convert_date_to_doe( start_date )
- msg( "D70", "get_instance_start_dates: " \
- "\n instance_start_date: " instance_start_date \
- "\n instance_end_date: " instance_end_date \
- "\n days_spanned: " days_spanned \
- )
- # Always want the first instance if it is in the output time range
- if ( in_output_range_by_date( instance_start_date, instance_end_date ) )
- my_return = my_return " " instance_start_date
- # For each event in the series up to the end of the output time range
- for ( i = 1; Count == 0 || i <= Count; i++ ) # "Count == 0" is for UNTIL
- {
- if ( Freq == "MONTHLY" )
- {
- # Increment by month(s)
- last_instance_start_date = instance_start_date
- last_instance_end_date = instance_end_date
- instance_start_date = \
- date_math_add_months( instance_start_date, Interval )
- instance_end_date = \
- date_math_add_months( instance_end_date, Interval )
- if ( Byday_list != "" )
- my_return = \
- my_return get_instance_start_dates_for_month_byday( \
- instance_start_date, days_spanned, until_doe \
- )
- else
- {
- for ( j = 1; instance_start_date == 0; )
- { # Fell on non-existent Feb 29th
- instance_start_date = \
- date_math_add_months( last_instance_start_date, \
- ++j * Interval )
- instance_end_date = \
- date_math_add_months( last_instance_end_date, \
- j * Interval )
- }
- if ( instance_end_date == 0 )
- { # Adjust non-existent 29 Feb to 1 Mar
- last_instance_doe = \
- convert_date_to_doe( last_instance_end_date )
- instance_end_date = \
- date_math_add_months( \
- convert_doe_to_date( last_instance_end_doe + 1 ),
- j * Interval \
- )
- }
- if ( in_output_range_by_date( instance_start_date, \
- instance_end_date ) \
- )
- my_return = my_return " " instance_start_date
- }
- }
- else if ( Freq == "WEEKLY" )
- {
- # Increment by weeks(s)
- instance_start_date = \
- date_math_add_weeks( instance_start_date, Interval )
- instance_end_date = \
- date_math_add_weeks( instance_end_date, Interval )
- if ( Byday_list != "" )
- # If instance start date is after the output range, earlier
- # in the week may not be
- my_return = my_return \
- get_instance_start_dates_for_week_byday( \
- instance_start_date, until_doe \
- )
- else
- {
- if ( in_output_range_by_date( instance_start_date, \
- instance_end_date ) \
- )
- my_return = my_return " " instance_start_date
- }
- }
- else
- { # YEARLY
- instance_start_date = \
- date_math_add_years( instance_start_date, Interval )
- instance_end_date = \
- date_math_add_years( instance_end_date, Interval )
- }
- if ( instance_start_date > Output_date_range_end_date ) break
- }
- msg( "D50", "get_instance_start_dates: returning: " my_return )
- return my_return
- }
- }
- function get_instance_start_dates_for_month_byday ( \
- start_date, days_spanned, until_doe, \
- \
- byday, bydays, doe, month_end_doe, first_char, i, i_max, last_doe,
- month_start_doe, my_return, negative, ordinal, weekday \
- )
- {
- msg( "D50", "get_instance_start_dates_for_month_byday: called with: " \
- "\n start_date: " start_date \
- "\n days_spanned: " days_spanned \
- "\n until_doe: " until_doe \
- )
- my_return = ""
- # For MONTHLY FREQs, Byday_list is a list of [+-]<ordinal><weekday>
- i_max = split( Byday_list, bydays, "," )
- for ( i = 1; i <= i_max; i++ )
- {
- byday = bydays[i]
- first_char = substr( bydays[i], 1, 1 )
- negative = 0
- if ( first_char == "-" )
- {
- negative = 1
- byday = substr( byday, 2 )
- }
- else if ( first_char == "+" )
- byday = substr( byday, 2 )
- weekday = substr( byday, length( byday ) - 1 )
- ordinal = substr( byday, 1, length( byday ) - 2 )
- month_end_doe = convert_date_to_doe( get_month_end_date( start_date ) )
- if ( until_doe && until_doe < month_end_doe )
- last_doe = until_doe
- else last_doe = month_end_doe
- month_start_doe = convert_date_to_doe( get_month_start_date( start_date ) )
- occurrence = 0
- msg( "D60", "get_instance_start_dates_for_month_byday: " \
- "\n negative: " negative \
- "\n weekday: " weekday \
- "\n ordinal: " ordinal \
- "\n month_start_doe: " month_start_doe \
- "\n last_doe: " last_doe \
- )
- if ( negative )
- {
- # Search backwards from last day of month in output date range
- for ( doe = last_doe; doe >= month_start_doe; doe-- )
- {
- if ( convert_doe_to_weekday( doe ) == weekday )
- {
- if ( ++occurrence == ordinal )
- {
- if ( in_output_range_by_doe( doe, doe + days_spanned ) )
- my_return = my_return " " convert_doe_to_date( doe )
- break
- }
- }
- }
- msg( "D100", \
- "get_instance_start_dates_for_month_byday: my_return: " \
- my_return \
- )
- }
- else
- {
- # Search forwards from first day of month to last day of output date
- # range
- for ( doe = month_start_doe; doe <= last_doe; doe++ )
- {
- if ( convert_doe_to_weekday( doe ) == weekday )
- {
- if ( ++occurrence == ordinal )
- {
- if ( in_output_range_by_doe( doe, doe + days_spanned ) )
- my_return = my_return " " convert_doe_to_date( doe )
- break
- }
- }
- }
- msg( "D100", \
- "get_instance_start_dates_for_month_byday: my_return: " \
- my_return \
- )
- }
- }
- msg( "D50", \
- "get_instance_start_dates_for_month_byday: returning: " my_return )
- return my_return
- }
- function get_instance_start_dates_for_week_byday ( start_date, until_doe, \
- doe, i, i_max, j, week_start_doe, my_return, weekday \
- )
- {
- msg( "D50", "get_instance_start_dates_for_week_byday: called with: " \
- "\n start_date: " start_date \
- "\n until_doe: " until_doe \
- )
- my_return = ""
- # For WEEKLY FREQs, Byday_list is a list of day names
- i_max = split( Byday_list, bydays, "," )
- for ( i = 1; i <= i_max; i++ )
- {
- weekday = bydays[i]
- week_start_doe = get_week_start_doe( start_date )
- for ( j = 0; j < 6; j++ )
- {
- doe = week_start_doe + j
- if ( until_doe && doe > until_doe ) break
- if ( doe > Output_date_range_end_doe ) break
- if ( convert_doe_to_weekday( doe ) == weekday )
- {
- my_return = my_return " " convert_doe_to_date( doe )
- break
- }
- }
- }
- msg( "D50", \
- "get_instance_start_dates_for_week_byday: returning: " my_return )
- return my_return
- }
- function get_month_end_date ( date, \
- y, m, d )
- {
- y = substr( date, 1, 4 )
- m = substr( date, 5, 2 )
- if ( m == "02" && leap_year( y ) ) d = 29
- else d = Month_day_counts[m + 0]
- return sprintf( "%d%02d%02d", y, m, d )
- }
- function get_month_start_date ( date )
- {
- return substr( date, 1, 6) "01"
- }
- function get_week_start_doe ( date, \
- doe, weekday )
- {
- doe = convert_date_to_doe( date )
- for ( i = 0; i < 7; i++ )
- {
- msg( "D100", "get_week_start_doe:" \
- " convert_doe_to_weekday( " doe " - " i " ): " \
- convert_doe_to_weekday( doe - i ) \
- )
- if ( convert_doe_to_weekday( doe - i ) == Wkst ) return doe - i
- }
- msg( "E", "get_week_start_doe: did not find WKST (" Wkst \
- ") after looking back a full week" )
- }
- function getopt(argc, argv, options, thisopt, i)
- {
- # From http://www.gnu.org/software/gawk/manual/gawk.html#Getopt-Function
- # Modified to return option letter of any invalid option instead of ?
- if (length(options) == 0) # no options given
- return -1
- if (argv[Optind] == "--") { # all done
- Optind++
- _opti = 0
- return -1
- } else if (argv[Optind] !~ /^-[^: \t\n\f\r\v\b]/) {
- _opti = 0
- return -1
- }
- if (_opti == 0)
- _opti = 2
- thisopt = substr(argv[Optind], _opti, 1)
- Optopt = thisopt
- i = index(options, thisopt)
- if (i == 0) {
- if (Opterr)
- printf("%c -- invalid option\n",
- thisopt) > "/dev/stderr"
- if (_opti >= length(argv[Optind])) {
- Optind++
- _opti = 0
- } else
- _opti++
- # Local modification
- #return "?"
- return thisopt
- }
- if (substr(options, i + 1, 1) == ":") {
- # get option argument
- if (length(substr(argv[Optind], _opti + 1)) > 0)
- Optarg = substr(argv[Optind], _opti + 1)
- else
- Optarg = argv[++Optind]
- _opti = 0
- } else
- Optarg = ""
- if (_opti == 0 || _opti >= length(argv[Optind])) {
- Optind++
- _opti = 0
- } else
- _opti++
- return thisopt
- }
- function get_vevent ( \
- i, n_lines \
- )
- {
- Dtend_date = ""
- Dtstart = ""
- Duration_days = 0
- Recurrence = 0
- n_lines = 0
- Lines[++n_lines] = $0
- while ( $0 != "END:VEVENT" )
- {
- my_getline()
- if ( $1 == "RRULE")
- {
- Recurrence = 1
- parse_rrule( $0 )
- }
- else if ( $1 ~ /^X-ORAGE-/ )
- continue
- else if ( $1 == "ATTACH" )
- continue
- else
- {
- Lines[ ++n_lines ] = $0
- if ( $1 == "ACTION" )
- {
- if ( Audio_alarm )
- Lines[ n_lines ] = "ACTION:AUDIO"
- else if ( Visual_alarm )
- Lines[ n_lines ] = "ACTION:DISPLAY"
- else if ( $1 == "PROCEDURE" )
- Lines[ n_lines ] = "ACTION:AUDIO" # Sane default
- }
- else if ( $1 == "DTEND" )
- {
- parse_dtend( $0 )
- Dtend_line = n_lines
- }
- else if ( $1 == "DTSTART" )
- {
- parse_dtstart( $0 )
- Dtstart_line = n_lines
- }
- else if ( $1 == "DURATION" )
- parse_duration( $0 )
- }
- }
- if ( Debug_level )
- {
- msg( "D1", "get_vevent: Lines:" )
- for ( i = 1; i <= n_lines; i++ )
- msg( "D", i " " Lines[ i ] )
- msg( "D10", "get_vevent: Dtstart_date: " Dtstart_date \
- ", Dtend_date: " Dtend_date )
- }
- return n_lines
- }
- function in_output_range_by_date ( start_date, end_date )
- {
- if ( start_date <= Output_date_range_end_date &&
- end_date >= Output_date_range_start_date \
- )
- return 1
- else
- return 0
- }
- function in_output_range_by_doe ( start_doe, end_doe )
- {
- if ( start_doe <= Output_date_range_end_doe &&
- end_doe >= Output_date_range_start_doe ) return 1
- else return 0
- }
- function initialise ( \
- array, cmdline, i, now, opt )
- {
- Version = "0.1"
- # Get last component of path by which this script called
- # Has to be done now in case needed for the -h help option
- getline cmdline < "/proc/self/cmdline"
- split( cmdline, array, "\0" )
- My_name = substr( array[3], match( array[3], /\/[^\/]*$/ ) + 1 )
- # Parse command line
- Audio_alarm = 0
- Opterr = 0 # Suppress getopt( ) error messages
- Optind = 1 # Skip ARGV[0]
- Debug_level = 0
- Visual_alarm = 0
- while ( ( opt = getopt(ARGC, ARGV, "ad:hVv")) != -1)
- {
- msg( "D", "initialise: opt: " opt )
- if ( opt == "a" )
- {
- Audio_alarm = 1
- Visual_alarm = 0
- }
- else if ( opt == "d" )
- {
- Debug_level = Optarg
- }
- else if ( opt == "h" )
- {
- usage( "verbose" )
- exit 0
- }
- else if ( opt == "V" )
- {
- msg( "I", My_name " version " Version )
- exit 0
- }
- else if ( opt == "v" )
- {
- Audio_alarm = 0
- Visual_alarm = 1
- }
- else
- {
- usage( "quiet" )
- msg( "E", "invalid command line option: " opt )
- }
- }
- for ( i = 1; i < Optind; i++ ) ARGV[i] = ""
- # Log startup
- "date +%Y/%m/%d@%H:%M:%S" | getline now
- cmdline = substr( cmdline, length( array[1] ) + length( array[2] ) + 3 )
- gsub( "\0", " ", cmdline )
- msg( "I", "Started version " Version " at " now " with command line " cmdline )
- # Set up output date range
- "date --date='-1 month' +%Y%m%d" | getline Output_date_range_start_date
- Output_date_range_start_doe = \
- convert_date_to_doe( Output_date_range_start_date )
- "date --date='+2 month' +%Y%m%d" | getline Output_date_range_end_date
- Output_date_range_end_doe = convert_date_to_doe( Output_date_range_end_date )
- msg( "D1", "initialise: output date range: " Output_date_range_start_date \
- " to " Output_date_range_end_date )
- msg( "D10", "initialise: output date range (doe): " \
- Output_date_range_start_doe " to " Output_date_range_end_doe )
- # Misc. variable initialisation
- split( "31,28,31,30,31,30,31,31,30,31,30,31", Month_day_counts, "," )
- split( "SU,MO,TU,WE,TH,FR,SA", Weekdays, "," )
- }
- function leap_year( y )
- {
- return ( ( y % 4 ) == 0 && ( y % 100 ) != 0 )
- }
- function modify_dtend_date ( dtend_date, dtend_line )
- {
- Lines[dtend_line] = sub( /:.*T/, ":" dtend_date "T", Lines[dtend_line] )
- }
- function modify_dtstart_date ( start_date )
- {
- sub( /:.*T/, ":" start_date "T", Lines[Dtstart_line] )
- }
- function msg ( msg_class, msg_text,
- exit_flag, my_debug_level )
- {
- exit_flag = 0
- if ( msg_class ~ /^D/ )
- {
- if ( ! Debug_level ) return
- if ( length( msg_class ) > 1 ) my_debug_level = substr( msg_class, 2 )
- else my_debug_level = 1 # Sane default
- if ( my_debug_level > Debug_level ) return
- msg_text = "DEBUG: " msg_text
- }
- else if ( msg_class == "I" )
- {
- }
- else if ( msg_class == "W" )
- {
- msg_text = "WARNING: " msg_text
- }
- else if ( msg_class == "E" )
- {
- msg_text = "ERROR: " msg_text
- exit_flag = 1
- }
- else
- {
- msg_text = "ERROR: msg: called with invalid message class " msg_class \
- "(and message text: " msg_text " )"
- exit_flag = 1
- }
- print msg_text > "/dev/stderr"
- if ( exit_flag ) exit 1
- }
- function my_getline ()
- {
- if ( getline <= 0 ) {
- m = "unexpected EOF or error"
- m = (m ": " ERRNO)
- print m > "/dev/stderr"
- exit 1
- }
- }
- function parse_dt ( line,
- array )
- {
- split( line, array, ":" )
- return array[2]
- }
- function parse_dt_date ( dt,
- array )
- {
- split( dt, array, "T" )
- return array[1]
- }
- function parse_dt_time ( dt,
- array )
- {
- split( dt, array, "T" )
- return array[2]
- }
- function parse_dtend ( line )
- {
- Dtend = parse_dt( line )
- Dtend_date = parse_dt_date( Dtend )
- Dtend_time = parse_dt_time( Dtend )
- msg( "D30", "parse_dtend: Dtend_date is " Dtend_date ". Dtend_time is " \
- Dtend_time )
- }
- function parse_dtstart ( line )
- {
- dtstart = parse_dt( line )
- Dtstart_date = parse_dt_date( dtstart )
- Dtstart_time = parse_dt_time( dtstart )
- Dtstart_time_in_days = convert_time_to_days( Dtstart_time )
- msg( "D", "parse_dtstart: Dtstart_date: " Dtstart_date \
- ", Dtstart_time: " Dtstart_time \
- ", Dtstart_time_in_days: " Dtstart_time_in_days )
- }
- function parse_duration ( line,
- array, duration, duration_time )
- {
- split( line, array, ":" )
- duration = array[2]
- if ( substr( duration, 1, 1 ) != "P" )
- msg( "E", "DURATION value does not begin with P: " line )
- duration = substr( duration, 2 )
- if ( split( duration, array, "T" ) > 1 )
- {
- # Orage does not set durations in weeks
- Duration_days = array[1]
- duration = array[2]
- }
- else
- {
- Duration_days = ""
- Duration = array[1]
- }
- duration = convert_duration_to_days( duration )
- Duration_days = Duration_days + duration
- msg( "D", "parse_duration: Duration_days:" Duration_days )
- }
- function parse_rrule ( line,
- i, keyword, keyvalue, n_rule_parts, rule_parts )
- {
- # Orage supports:
- # Frequency: Daily, Weekly, Monthly, Yearly
- # Interval: 1,2,3 ...
- # Repeat: Forever, Count, Until
- # Weekdays (except daily frequency)
- # Which day (monthly and yearly frequency only): + or - 1,2,3 ...
- # Initialise
- Byday_list = ""
- Bymonth_list = ""
- Bymonthday_list = ""
- Bysetpos_list = ""
- Byyearday_list = ""
- Count = 0
- Interval = 1
- Until = ""
- Until_date = ""
- Wkst = "MO"
- # Parse
- # TODO: why treat FREQ differently from the others?
- n_rule_parts = split( line, rule_parts, /[:;]/ )
- Freq = gensub( /FREQ=/, "", 1, rule_parts[2] )
- for ( i = 3; i <= n_rule_parts; i++ )
- {
- keyword = gensub( /=.*/, "", 1, rule_parts[i] )
- keyvalue = gensub( /.*=/, "", 1, rule_parts[i] )
- if ( keyword == "BYDAY" ) Byday_list = keyvalue
- else if ( keyword == "BYMONTH" ) Bymonth_list = keyvalue
- else if ( keyword == "BYMONTHDAY" ) Bymonthday_list = keyvalue
- else if ( keyword == "BYSETPOS" ) Bysetpos_list = keyvalue
- else if ( keyword == "BYYEARDAY" ) Byyearrday_list = keyvalue
- else if ( keyword == "COUNT" ) Count = keyvalue
- else if ( keyword == "INTERVAL" ) Interval = keyvalue
- else if ( keyword == "UNTIL" )
- {
- Until = keyvalue
- Until_date = substr( Until, 1, 8 )
- }
- else if ( keyword == "WKST" )
- Wkst = keyvalue
- else
- msg( "E", "unrecognised RRULE keyword " keyword " in " line )
- }
- msg( "D",
- "parse_rrule:" \
- "\n Freq = " Freq \
- "\n Byday_list = " Byday_list \
- "\n Bymonth_list = " Bymonth_list \
- "\n Bymonthday_list = " Bymonthday_list \
- "\n Bysetpos_list = " Bysetpos_list \
- "\n Byyearday_list = " Byyearday_list \
- "\n Count = " Count \
- "\n Interval = " Interval \
- "\n Until = " Until \
- "\n Until_date = " Until_date \
- "\n Wkst = " Wkst )
- }
- function usage ( verbosity )
- {
- msg( "I", "Usage:" \
- "\n " My_name " [-a] [-d debug_level] [-h] [-V] [-v]" \
- )
- if ( verbosity == "verbose" )
- msg( "I", " Where:" \
- "\n -a sets all alarms to audio" \
- "\n -d sets the debug level" \
- "\n 1 to 100 gives progressively more detail" \
- "\n -h prints this help message and exits" \
- "\n -v sets all alarms to visual" \
- "\n -V prints the version number and exits" \
- "\n If both -a and -v are given, the last is effective." \
- "\n Reads an Orage export .ics file from stdin." \
- "\n Writes a .ics file to suit gnokii to stdout." \
- "\n Writes logging to stderr." \
- )
- }
- function write_vevent ( n_lines,
- i )
- {
- msg( "D10", "write_vevent: writing event" )
- for ( i = 1; i <= N_lines; i++ ) print Lines[i]
- }
- BEGIN {
- FS = "[:;]"
- initialise()
- for ( ; ; )
- {
- # Replicate input to output until the next VEVENT or EoF
- get_begin_vevent()
- # Input a single VEVENT
- N_lines = get_vevent()
- # Output any VEVENTs in the time range
- if ( Recurrence ) do_vevents( )
- else do_vevent( )
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement