Convert Recurrence Rule to Descriptive text

4 posts, 0 answers
  1. Kevin Price
    Kevin Price avatar
    49 posts
    Member since:
    Sep 2012

    Posted 15 Sep 2010 Link to this post


    RadControls version

    Version independent
    .NET version

    Visual Studio version

    programming language

    browser support

    all browsers supported by RadControls

    This started as a question or two posted in forums, but will post code sample here as well as it seems useful. The goal was to take a RecurrenceRule string from the database for a given appointment and convert that into a plain English statement that could be used in an email for the appointment.
    The attached zip file contains a static class (RecRuleParser) that does this.
    Usage: RecRuleParser.ParseRRule(<RecurrenceRule string from DB>, <boolean to include exceptions>);
    DTSTART:20100527T040000Z  DTEND:20100528T040000Z  RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TH  EXDATE:20100527T040000Z,20100813T040000Z 
    RESULT: Occurs weekly every Thursday starting on 5/27/2010 at 12:00 AM except on 5/27/2010 12:00:00 AM and 8/13/2010 12:00:00 AM

    RESULT:Occurs monthly on the 4th Saturday  and Sunday starting on 9/14/2010 at 2:00 PM

    It may not be perfect, but it is working quite well for us.

  2. Dan Lehmann
    Dan Lehmann avatar
    103 posts
    Member since:
    May 2011

    Posted 07 Jul 2011 Link to this post

    Thanks very much. I'm using this now. I converted to VB and had to make a couple changes involving the .Split() method to get it to work.

    I found a bug with recurrence when the rule is set to the Last day of week or month, it renders as the -1 day. I am working on a fix. For now, here is the vb:

    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.Globalization
    Public Class RecRuleParser
        Private Sub New()
        End Sub
        Public Shared Function ParseRRule(rRule As String, showExceptions As Boolean) As String
            If rRule = "" Then
                Return ""
            End If
            Dim parsed As String = String.Empty
            'set up the return string
            Dim englishStatement As New StringBuilder()
            'Break it into basic parts
            Dim elements As String() = rRule.Split(vbCrLf)
            'it's double spaced in the db, so deal with it accordingly
            Dim startDate As String = elements(0).Trim
            Dim endDate As String = elements(1).Trim
            Dim recRule As String = elements(2).Trim
            Dim recExcs As String = String.Empty
            'check for exceptions
            If elements.Length > 3 Then
                recExcs = elements(3).Trim
            End If
            'Attempt to parse the zulu dates into something else
            Dim dtStart As DateTime = DateTime.ParseExact(getElemValue(startDate), "yyyyMMddTHHmmssZ", System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)
            Dim dtEnd As DateTime = DateTime.ParseExact(getElemValue(endDate), "yyyyMMddTHHmmssZ", System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)
            Dim tsEnd As TimeSpan = dtEnd.Subtract(dtStart)
            'Now work with the recurrence rule
            Dim rruleElems As New Dictionary(Of String, String)()
            'Convert the string to a dictionary so we can find things easy
            parsed = getElemValue(recRule)
            'no need having unnecessarily declared strings
            elements = parsed.Split(";"c)
            For i As Integer = 0 To elements.Length - 1
                Dim tmp As String() = elements(i).Split("="c)
                rruleElems.Add(tmp(0), tmp(1))
            englishStatement.Append("Occurs " & rruleElems("FREQ").ToLower())
            Dim calType As String = String.Empty
            'need a scratchpad
            'start translating into English
            Dim timeToAdd As Integer = 0
                timeToAdd = Convert.ToInt32(rruleElems("COUNT"))
                timeToAdd = 0
            End Try
            Dim days As String()
            Select Case rruleElems("FREQ").ToLower()
                Case "daily"
                    days = rruleElems("BYDAY").Split(","c)
                    dtEnd = dtEnd.AddDays(timeToAdd)
                    calType = "days"
                    Exit Select
                Case "weekly"
                    calType = "weeks"
                    dtEnd = dtEnd.AddDays(timeToAdd * 7)
                        days = rruleElems("BYDAY").Split(","c)
                        'just in case we missed something on this one
                        Throw New Exception("Error while processing Recurrence Rule")
                    End Try
                    Exit Select
                Case "monthly"
                    calType = "months"
                    dtEnd = dtEnd.AddMonths(timeToAdd)
                    'see if it's positional
                        Dim bsp As String = getDayEnding(rruleElems("BYSETPOS"))
                        englishStatement.Append(" on the " & bsp & " " & parseDayNames(rruleElems("BYDAY").Split(","c)).Replace(" every ", ""))
                        'Ok, no BYSETPOS, let's go for BYMONTHDAY
                        Dim bsp As String = getDayEnding(rruleElems("BYMONTHDAY"))
                        englishStatement.Append(" on the " & bsp & " day of each month")
                    End Try
                    Exit Select
                Case "yearly"
                    calType = "years"
                    dtEnd = dtEnd.AddYears(timeToAdd)
                    'looks a lot like monthly....
                    Dim mName As String = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(Convert.ToInt32(rruleElems("BYMONTH")))
                    'see if it's positional
                        Dim bsp As String = getDayEnding(rruleElems("BYSETPOS"))
                        englishStatement.Append(" on the " & bsp & " " & parseDayNames(rruleElems("BYDAY").Split(","c)).Replace(" every ", "") & " of " & mName)
                        'Ok, no BYSETPOS, let's go for BYMONTHDAY
                        Dim bsp As String = getDayEnding(rruleElems("BYMONTHDAY"))
                        englishStatement.Append(" on the " & bsp & " day of " & mName)
                    End Try
                    Exit Select
                Case "hourly"
                    calType = "hours"
                    dtEnd = dtEnd.AddHours(timeToAdd)
                    Exit Select
                Case Else
                    Exit Select
            End Select
            englishStatement.Append(" starting on " & dtStart.ToLocalTime().ToShortDateString() & " at " & dtStart.ToLocalTime().ToShortTimeString())
            If timeToAdd > 0 Then
                englishStatement.Append(" for the next " & rruleElems("COUNT") & " " & calType)
                englishStatement.Append(" ending on " & dtEnd.ToLocalTime().ToShortDateString() & " at " & dtStart.AddHours(tsEnd.Hours).ToLocalTime().ToShortTimeString())
            End If
            If recExcs.Length > 0 AndAlso showExceptions Then
                Dim excs As String() = recExcs.Split(":"c)(1).Split(","c)
                Dim retString As String = String.Empty
                englishStatement.Append(" except on ")
                For r As Integer = 0 To excs.Length - 1
                    'we'll use dtEnd, it's not doing anything now
                    dtEnd = DateTime.ParseExact(excs(r), "yyyyMMddTHHmmssZ", System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal).ToLocalTime()
                    If r < excs.Length AndAlso excs.Length > 2 Then
                        retString += dtEnd & ","
                        If r < excs.Length - 1 AndAlso excs.Length = 2 Then
                            retString += dtEnd & " and "
                            retString += dtEnd
                        End If
                    End If
            End If
            Return englishStatement.ToString()
        End Function
        Private Shared Function getElemValue(elem As String) As String
            'just easier than writing split all over the place
            Dim elems As String() = elem.Split(":"c)
            Return elems(1).Trim()
        End Function
        Private Shared Function getDayName(day As String) As String
            'pretty self explanatory
            Select Case day
                Case "MO"
                    Return "Monday"
                Case "TU"
                    Return "Tuesday"
                Case "WE"
                    Return "Wednesday"
                Case "TH"
                    Return "Thursday"
                Case "FR"
                    Return "Friday"
                Case "SA"
                    Return "Saturday"
                Case "SU"
                    Return "Sunday"
                Case Else
                    Return ""
            End Select
        End Function
        Private Shared Function parseDayNames(days As String()) As String
            Dim retString As String = String.Empty
            If True Then
                If days.Length < 7 Then
                    retString += " every"
                    For d As Integer = 0 To days.Length - 1
                        days(d) = getDayName(days(d))
                        If d = days.Length - 1 AndAlso days.Length > 1 Then
                            days(d) = " and " & days(d)
                            If days.Length > 2 Then
                                days(d) += ","
                            End If
                        End If
                        retString += " " & days(d)
                End If
            End If
            Return retString
        End Function
        Private Shared Function getDayEnding(d As String) As String
            'tried to avoid a big ugly if statement
            'handle the events on the "n"th day of the month
            If d.EndsWith("1") AndAlso d <> "11" Then
                d += "st"
            End If
            If d.EndsWith("2") AndAlso d <> "12" Then
                d += "nd"
            End If
            If d.EndsWith("3") AndAlso d <> "13" Then
                d += "rd"
            End If
            If d.Length < 3 Then
                'hasn't been appended yet
                d += "th"
            End If
            Return d
        End Function
    End Class
  3. Paulson Palmer
    Paulson Palmer avatar
    3 posts
    Member since:
    Sep 2012

    Posted 16 Apr 2015 in reply to Kevin Price Link to this post

  4. Mehmet
    Mehmet avatar
    100 posts
    Member since:
    Jun 2014

    Posted 22 Nov 2015 Link to this post

    Hi Kevin , thanks for your post.

    I've implemented it in my solution however, it throws some exception which doesn't allow the class to generate the correct statement. For instance; if i don't pick "End After xx occurrence" this rule doesn't send out a "Count" parameter which impacts on the end date and the class doesn't add the info of the end date in the statement. 
    There is another issue which seems a bit strange. The recurrence control creates the year of the end date as 9999. Do you know how to handle this issue?
    Thank you

Back to Top