Regular Expression to parse RecurrenceRule

8 posts, 1 answers
  1. Brian Mackey
    Brian Mackey avatar
    3 posts
    Member since:
    Aug 2009

    Posted 26 Aug 2009 Link to this post

    In short, I need to write a regular expression to parse the data from the RuccurenceRule.  I've started it with 1 row of sample data:

    DTSTART:(?<StartDate>\d{8}).* DTEND:(?<EndDate>\d{8}).* FREQ=(?<Frequency>[A-Z]{0,10});INTERVAL=(?<Interval>\d{1,3});BYDAY=(?<ByDay>[A-Z]{1,3})

    Because this is actually handled inside the scheduler control itself, I assume Telerik already has this pattern nice, pretty, and tested.  If so, would you mind sharing it?


    We have the requirement to color code appointments (recurring and not) according to whether a particular file has been received that has been associated with that appointment.  This is extending beyond the functionality a bit, so we need to pretty much duplicate the recurrence handling in order to associate an appointment with a file received via datetime.
  2. Answer
    Peter
    Admin
    Peter avatar
    6637 posts

    Posted 27 Aug 2009 Link to this post

    Hi Brian,

    Yes, you can parse the recurrence rule using RadScheduler. The following resources show how to do this:

    http://www.telerik.com/help/aspnet-ajax/schedule_serversideworkingwithrecurringappointments.html

    http://www.telerik.com/support/kb/aspnet-ajax/scheduler/how-to-display-all-radscheduler-appointments-in-gridview.aspx

    Let us know if you have further questions.


    Greetings,
    Peter
    the Telerik team

    Instantly find answers to your questions on the new Telerik Support Portal.
    Watch a video on how to optimize your support resource searches and check out more tips on the blogs.
  3. UI for ASP.NET Ajax is Ready for VS 2017
  4. Kevin Price
    Kevin Price avatar
    49 posts
    Member since:
    Sep 2012

    Posted 12 Apr 2010 Link to this post

    Peter -
    I have gained a similar requirement, and am showing a single appt on a page. Without having to load a scheduler, is there an acceptable way to parse the recurrencerule to show as simple text? Ex. Occurs every Monday until ....

    Thanks,
    Kevin
  5. T. Tsonev
    Admin
    T. Tsonev avatar
    2770 posts

    Posted 13 Apr 2010 Link to this post

    Hello Kevin,

    We don't have such function ready, but you can use RecurrenceRule.TryParse to obtain the recurrence object and then format the user-friendly string based on the object type and properties.

    I hope this helps.

    Best wishes,
    Tsvetomir Tsonev
    the Telerik team

    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
  6. Kevin Price
    Kevin Price avatar
    49 posts
    Member since:
    Sep 2012

    Posted 14 Sep 2010 Link to this post

    Peter,
    To avoid duplicating efforts  (again :) ) I'll keep my posts on this thread. Please check the below code (RecRuleParser.cs).
    It does a pretty good job of converting the RecurrenceRule values from the database to a plain-text English statement.
    Ex:
    DTSTART:20100526T040000Z  DTEND:20100527T040000Z  RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE  EXDATE:20100526T040000Z,20100812T040000Z 

    Returns:
    Occurs weekly every Wednesday starting on 5/26/2010 at 12:00 AM except on 5/26/2010 12:00:00 AM and 8/12/2010 12:00:00 AM (as it converts to local time) I included code as well to try and spell out the exceptions - source is quite easily modified and full of comments (and commentary)

    DTSTART:20100622T160000Z  DTEND:20100622T170000Z  RRULE:FREQ=WEEKLY;COUNT=3;INTERVAL=1;BYDAY=TU  EXDATE:20100622T160000Z,20100706T160000Z 
    Returns:
    Occurs weekly every Tuesday starting on 6/22/2010 at 12:00 PM for the next 3 weeks ending on 7/13/2010 at 1:00 PM except on 6/22/2010 12:00:00 PM and 7/6/2010 12:00:00 PM

    usage: myOutputString = RecRuleParser.ParseRule(string <RecurrenceRuleText>, bool <showExceptions>);

    With a little work, and a resource file, probably wouldn't be too hard to support additional language. Hope this helps. It may not

    Thanks,
    Kevin

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Globalization;
    //You might want to change this
    namespace RRuleParser
    {
        public static class RecRuleParser
        {
            public static string ParseRRule(string rRule, bool showExceptions)
            {
                string parsed = string.Empty;//set up the return string
                StringBuilder englishStatement = new StringBuilder();
                //Break it into basic parts
                string[] elements = rRule.Split(' ');
                //it's double spaced in the db, so deal with it accordingly
                string startDate = elements[0];
                string endDate = elements[2];
                string recRule = elements[4];
                string recExcs = string.Empty;
                //check for exceptions
                if (elements.Length > 5)
                {
                    recExcs = elements[6];
                }
                //Attempt to parse the zulu dates into something else
                DateTime dtStart = DateTime.ParseExact(getElemValue(startDate), "yyyyMMddTHHmmssZ", System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
                DateTime dtEnd = DateTime.ParseExact(getElemValue(endDate), "yyyyMMddTHHmmssZ", System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
                TimeSpan tsEnd = dtEnd.Subtract(dtStart);
                //Now work with the recurrence rule
                Dictionary<string, string> rruleElems = new Dictionary<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(';');
                for (int i = 0; i < elements.Length; i++)
                {
                    string[] tmp = elements[i].Split('=');
                    rruleElems.Add(tmp[0], tmp[1]);
                }
                englishStatement.Append("Occurs " + rruleElems["FREQ"].ToLower());
                string calType = string.Empty; //need a scratchpad
                //start translating into English
                int timeToAdd = 0;
                try
                {
                    timeToAdd = Convert.ToInt32(rruleElems["COUNT"]);
                }
                catch
                {
                    timeToAdd = 0;
                }
                switch (rruleElems["FREQ"].ToLower())
                {
                    case "daily":
                        string[] days = rruleElems["BYDAY"].Split(',');
                        englishStatement.Append(parseDayNames(days));
                        dtEnd = dtEnd.AddDays(timeToAdd);
                        calType = "days";
                        break;
                    case "weekly":
                        calType = "weeks";
                        dtEnd = dtEnd.AddDays(timeToAdd * 7);
                        try
                        {
                            days = rruleElems["BYDAY"].Split(',');
                            englishStatement.Append(parseDayNames(days));
                        }
                        catch
                        {
                            //just in case we missed something on this one
                            throw new Exception("Error while processing Recurrence Rule");
                        }
                        break;
                    case "monthly":
                        calType = "months";
                        dtEnd = dtEnd.AddMonths(timeToAdd);
                        //see if it's positional
                        try
                        {
                            string bsp = getDayEnding(rruleElems["BYSETPOS"]);
                            englishStatement.Append(" on the " + bsp + " " + parseDayNames(rruleElems["BYDAY"].Split(',')).Replace(" every ", ""));
                        }
                        catch
                        {
                            //Ok, no BYSETPOS, let's go for BYMONTHDAY
                            string bsp = getDayEnding(rruleElems["BYMONTHDAY"]);
                            englishStatement.Append(" on the " + bsp + " day of each month");
                        }
                        break;
                    case "yearly":
                        calType = "years";
                        dtEnd = dtEnd.AddYears(timeToAdd);
                        //looks a lot like monthly....
                        string mName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(Convert.ToInt32(rruleElems["BYMONTH"]));
                        //see if it's positional
                        try
                        {
                            string bsp = getDayEnding(rruleElems["BYSETPOS"]);
                            englishStatement.Append(" on the " + bsp + " " + parseDayNames(rruleElems["BYDAY"].Split(',')).Replace(" every ", "") + " of " + mName);
                        }
                        catch
                        {
                            //Ok, no BYSETPOS, let's go for BYMONTHDAY
                            string bsp = getDayEnding(rruleElems["BYMONTHDAY"]);
                            englishStatement.Append(" on the " + bsp + " day of " + mName);
                        }
                        break;
                    case "hourly":
                        calType = "hours";
                        dtEnd = dtEnd.AddHours(timeToAdd);
                        break;
                    default:
                        break;
     
                }
                englishStatement.Append(" starting on " + dtStart.ToLocalTime().ToShortDateString() + " at " + dtStart.ToLocalTime().ToShortTimeString());
     
                if (timeToAdd > 0)
                {
                    englishStatement.Append(" for the next " + rruleElems["COUNT"] + " " + calType);
                    englishStatement.Append(" ending on " + dtEnd.ToLocalTime().ToShortDateString() + " at " + dtStart.AddHours(tsEnd.Hours).ToLocalTime().ToShortTimeString());
                }
                if (recExcs.Length > 0 && showExceptions)
                {
                    string[] excs = recExcs.Split(':')[1].Split(','); string retString = string.Empty;
                    englishStatement.Append(" except on ");
                    for (int r = 0; r < excs.Length; r++)
                    {
                        //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 && excs.Length > 2)
                        {
                            retString += dtEnd + ",";
                        }
                        else
                        {
                            if (r < excs.Length - 1 && excs.Length == 2)
                            {
                                retString += dtEnd + " and ";
                            }
                            else
                            {
                                retString += dtEnd;
                            }
                        }
                    }
                    englishStatement.Append(retString);
                }
                return englishStatement.ToString();
            }
            private static string getElemValue(string elem)
            {
                //just easier than writing split all over the place
                string[] elems = elem.Split(':');
                return elems[1].Trim();
            }
            private static string getDayName(string day)
            {
                //pretty self explanatory
                switch (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";
                    default:
                        return "";
                }
            }
            private static string parseDayNames(string[] days)
            {
                string retString = string.Empty;
                {
                    if (days.Length < 7)
                    {
                        retString += " every";
                        for (int d = 0; d < days.Length; d++)
                        {
                            days[d] = getDayName(days[d]);
     
                            if (d == days.Length - 1 && days.Length > 1)
                            {
                                days[d] = " and " + days[d];
                            }
                            else
                            {
                                if (days.Length > 2)
                                {
                                    days[d] += ",";
                                }
                            }
                            retString += " " + days[d];
                        }
                    }
                    return retString;
                }
            }
            private static string getDayEnding(string d)
            {
                //tried to avoid a big ugly if statement
                //handle the events on the "n"th day of the month
                if (d.EndsWith("1") && d != "11")
                {
                    d += "st";
                }
                if (d.EndsWith("2") && d != "12")
                {
                    d += "nd";
                }
                if (d.EndsWith("3") && d != "13")
                {
                    d += "rd";
                }
                if (d.Length < 3)//hasn't been appended yet
                {
                    d += "th";
                }
                return d;
            }
        }
    }
  7. T. Tsonev
    Admin
    T. Tsonev avatar
    2770 posts

    Posted 15 Sep 2010 Link to this post

    Hi Kevin,

    Thank you for posting the code here. I'm sure someone will find it helpful.

    As a token of gratitude for your involvement your Telerik points have been updated.

    Best wishes,
    Tsvetomir Tsonev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  8. Gustavo
    Gustavo  avatar
    1 posts
    Member since:
    Nov 2008

    Posted 05 Feb 2013 Link to this post

    Hello,
    I'm new using asp.net and i want to know how I can implement this code into the my web apps.  right now, I'm using RadScheduler for one of my applications and I have a small problem is that each follows: My application is a work order system which uses a calendar to place recurring orders, these orders are shipped one day before processing in order that they can be addressed. however I am trying to do that every time you mount a recurring type this command is saved in the database independently ie. If an order is placed to run for example every Friday for a month, I need to know how I can send a work order for each Friday of the month that was scheduled.


    Thanks you

    Gustavo
  9. Boyan Dimitrov
    Admin
    Boyan Dimitrov avatar
    1746 posts

    Posted 08 Feb 2013 Link to this post

    Hello Gustavo,

    I would recommend using the the RadScheduler AppointmentInsert server-side event to parse the current appointment recurrence rule. This way you could extract the information that would be helpful in order to calculate all appointment occurrences in the future and execute your custom logic to send those orders.
    Here you may find more information about AppointmentInsert server-side event.

    Regards,
    Boyan Dimitrov
    the Telerik team
    If you want to get updates on new releases, tips and tricks and sneak peeks at our product labs directly from the developers working on the RadControls for ASP.NET AJAX, subscribe to their blog feed now.
Back to Top
UI for ASP.NET Ajax is Ready for VS 2017