Using Liberty BASIC
Used by all major platforms—including Microsoft, Google and Apple—the .ics file format allows you to import, export and share calendar entries with other users. In this article, Jeff explains how .ics files work, and shares his project, using Liberty Basic to develop an app that generates .ics files.
One of the questions I get from my wife Beverly on a regular basis is, “You got anything going on?” to which I usually answer, “Let me check my calendar.” This inquiry will be used to make appropriate adjustments for a family supper together, should one of us be going out to a meeting or event. In these times of COVID-19, this may more likely be a Zoom (virtual) meeting. Although we have a paper calendar hanging on the wall, it’s rare that everything gets written there. Like anyone with a smartphone these days, its calendar app keeps us on track by reminding us of special events and those times when we need to be somewhere.
The calendar app lets you add events to and remove events from your calendar. We don’t think much about it. Being a Red Sox fan in Connecticut (sorry Yankee fans), I’m constantly encouraged to download their game schedule. If you choose to get that, a file is created that will add events to your calendar with all the scheduled home and away games for the year. How does all that work? Well, let’s find out!
THE .ICS FILE SPEC
The .ics file spec defines a calendar file format used by several email and calendar programs, including Microsoft Outlook, Google Calendar and Apple Calendar. This allows a user to publish and share calendar information on the Web and over email. These are in “plain text” format, which means they are human readable. The IETF (Internet Engineering Task Force) is responsible for the original iCalendar standards. RFC 2455 [1] has been retired, and a new entity, the Calendaring Extensions working group, is developing extensions to the iCalendar: RFC 5545 [2].
Let’s begin with the basic iCalendar format, since the extensions build off of that document. The iCalendar file contains an iCalendar object of individual lines of text, called content lines. Each content line has the same format, “<name>:<value>” and is delimited by a line break (CRLF). Note here that unless otherwise indicated, these are not case sensitive. The iCalendar object must begin with the object delimiter BEGIN:VCALENDAR
and end with an END:VCALENDAR
delimiter. This forms a wrapper for the iCalendar’s components. Each calendar component—event, to-do, journal, free/busy, time zone and alarm—is wrapped in a similar way with “begin” and “end” delimiters, that is, BEGIN:VEVENT
and END:VEVENT
. Each individual component (as well as the calendar overall) can have properties associated with it. These properties define an attribute of its respective components, such as DTSTART
(starting date/time).
CALENDAR COMPONENTS
All properties that fall within a component’s wrapper are associated with that component. A quick look at each of the components will help to understand the calendar object as a whole.
• VEVENT calendar component—a grouping of properties that represents a scheduled amount of time on a calendar
• VTODO calendar component—a grouping of properties that represent an action item or assignment
• VJOURNAL calendar component—a grouping of properties that represent one or more descriptive text notes associated with a particular calendar date
• VFREEBUSY calendar component—a grouping of properties representing either a request for a reply to a request for free or busy time information, or a published set of busy time information
• VTIMEZONE calendar component—a grouping of properties representing a location (time zone) via an offset from UTC (Coordinated Universal Time)
• VALARM calendar component—a grouping of properties representing a reminder or alarm for an event or a to-do. For example, it may be used to define a reminder for a pending event or an overdue to-do. This implies that the VALARM component can be part of (inside the wrapper of) another component.
— ADVERTISMENT—
—Advertise Here—
As you can see, an iCalendar object comprises one or more components, each of which can be made up of other components. The iCalendar object and all its components have beginning and ending delimiters, which make them easy to identify.
Let’s start with a simple example—an invitation to a picnic at my house on July 4th from 1 p.m. to 6 p.m. I’ll need to format this data appropriately.
My iCalendar object will use the event component, so my file can start with:
BEGIN:VCALENDAR
BEGIN:VEVENT
END:VEVENT
END:VCALENDAR
Note: The file will not have indented text. That said, I’m presenting it this way to highlight the nested structure.
The table shown in Figure 1 is my interpretation of information from RFC2455. The calendar object has two mandatory entries, VERSION
and PRODID
. There are two mandatory properties for an event, UID
and DTSTAMP
. It wouldn’t be much of an event without some basic data, so I’ll add some optional properties. From the description of the event above, I’ll add SUMMARY, LOCATION, DTSTART, DTEND
and maybe DESCRIPTION
. The addition of the mandatory and optional properties would look like Listing 1. Save this Listing 1 text file with a .ics file type and we’ve got it! If I click on this file, my Mail/Calendar/People app opens up with a new event displayed. I can save this to my calendar.
LISTING 1 – The calendar object has two mandatory entries: VERSION and PRODID. The optional entries are: SUMMARY, LOCATION, DTSTART, DTEND and DESCRIPTION.
BEGIN:VCALENDAR
VERSION:2.0
PRODID://from the bench column #371//
BEGIN:VEVENT
UID:20210115T105455@snet.net
DTSTAMP:20210115T105455
SUMMARY:Picnic
LOCATION:Crystal Lake
DTSTART:20210704T130000
DTEND:20210704T180000
DESCRIPTION:Bring swimming attire and a dish to share
END:VEVENT
END:VCALENDAR
— ADVERTISMENT—
—Advertise Here—
We can choose to build these files by hand or create some app to do this for us. I will use Liberty BASIC to build an app for this purpose, and look at more of the options we might want to include.
LIBERTY BASIC
Building an app for the PC can get pretty complicated for the casual user. Unless this is your profession, it can take an extraordinary amount of time to come up to speed with some of the tools available from Microsoft or other third-party vendors. I use Liberty BASIC when I need a PC app, because it is easy to learn and won’t set you back an arm and leg financially. I find I can usually get the job done quickly. The most time-consuming part of this application was coming up with what information I needed to present for the user to quickly put together a calendar event. I started off collecting the information I needed, and tried to arrange it in some sensible format.
One of the tools available with Liberty BASIC is the FreeForm GUI editor. This tool allows you to design a working window and then pick and place components in the window. You can drag them around and edit their properties, until you see a mock-up of what your app will look like. Figure 2 shows most of the components I’ve placed. Each is numbered in the order in which it was placed. When you double-click on a placed component, its properties pop up, and you can change them if you like. This property is for the combobox component “1” and will allow the user to choose the starting month from a drop-down list containing the months of the year. Other components used are statictext (as labels), textboxes (for typing in information), checkboxes (for selecting something), radio buttons (for choosing 1 of some) and buttons (to perform tasks).
Note that the properties I edited were the array name, which I call month$(), the .ext, which is the dot extension defining this combobox component for the main window, and the branch label, in which I continue to use the term startmonth—my way of identifying this component so I can easily find it when it is used in the code. This is much easier to identify than the default combobox1 label in the properties. Note that I did not use the array startmonth$(). I could have used that, but since several of the components can use the list of months, I chose the array month$(), which will be used for components 1, 3, 28 and 32.
Whenever possible, I like to place all my components on the same page. You’ll note that this application is pretty simple, unless you wish to create a recurring event. Just below the Description textbox, there is a Repeat checkbox. When the app begins, this will be unchecked and everything below it will be invisible. You will only see the information above this. We’ll start here.
FILL IN THE BLANK?
Three textboxes are displayed—Summary, Location and Description. You should fill in at least the Summary with whatever you would like displayed right on your calendar, such as “Bev’s birthday,” “Dentist” or “Shopping list.” The location might include where the event is taking place or even the URL of a map for driving directions. The description might include Bev’s age, which dentist or a list of items you need to purchase.
Most events start and end on the same day, but you can span multiple days, for example, if you’re taking a week-long vacation. Here you will need to adjust the first and last days of the event. An in-house business meeting probably won’t span multiple days, but you will most likely want to indicate the starting and ending times of the meeting. I limited the minutes to four entries, 0, 15, 30 and 45. Just to the right of the Start/End Date/Time is the All Day checkbox. When checked, the Times will be disregarded.
The last components on this simplified page are the Show As combobox and the buttons labeled Save and Exit. Some calendars show your status during an event. You can choose one from the pull-down list: busy, free, tentative or out-of-the-office—whichever is appropriate for your event. The button tasks are pretty clear, but we’ll look at Saving a bit closer. After all, that’s the whole purpose of this application.
SIMPLE EVENTS
In addition to the ability of the FreeForm GUI editor to allow the programmer to physically set up how the application will look to the user, it performs another important function. It can automatically produce the framework code necessary to support all the components. The component properties are used for all positioning and labeling of the required commands to display your designed layout. All routines are labeled—you just need to fill in any code that is required for each function. For instance, for the startmonth combobox described briefly earlier, the array month$()
needs to be created as shown in Listing 2.
dim month$(12)
month$(1) = "January"
month$(2) = "February"
month$(3) = "March"
month$(4) = "April"
month$(5) = "May"
month$(6) = "June"
month$(7) = "July"
month$(8) = "August"
month$(9) = "September"
month$(10) = "October"
month$(11) = "November"
month$(12) = "December"
LISTING 2 – All routines are labeled—you just need to fill in any code that is required for each function. For instance, for the startmonth combobox described briefly earlier, the array month$() needs to be created as shown here.
We need to dimension any array that is larger than the 10 elements. Then we define each entry in the array month$(1)
– month$(12)
. There are two ways we can identify elements in a combobox component: by index (in this case 1-12) or by an element’s contents, that is the defined “string.” It is more flexible to use the index whenever possible, even when the elements are text strings. In this case we are already familiar with using the index 1-12 as meaning the months January to December.
The branch property of a component indicates the name of a function that will be called when the user interfaces with the component. In this case, the routine [combobox.startmonthDoubleClick]
is called when the user clicks on the combobox
and clicks on one of the array choices, say July. The FreeForm GUI editor is smart enough to know it needs to define a branch. However, it is up to the user to define what code gets executed when the user chooses.
[combobox.startmonthDoubleClick] Print #main.combobox.startmonth,
"selectionindex? Startmonth"
Wait
You might think this just prints the text “selectionindex? startmonth.” However, it interacts with the combobox. The text within the string is actually a command, and in this case, it places the selected text’s selectionindex
into the variable startmonth
. The print statement used with a component allows us to interact with the component. You’ll see more of this later. Right now, we just want to remember what the user chose, and this is saved as an index (1-12) into the variable startmonth
.
— ADVERTISMENT—
—Advertise Here—
Initially, I set all dates and times default to the present. Therefore, the user need only change those that are appropriate for the event. The checkbox and radio button components have two branches, one for when the component is selected, and one for when it is deselected. The difference between the two is that every checkbox is independent, so it is selected and deselected on its own. When a radio button is selected all other (grouped) radio buttons are deselected. Only one in a group can be selected at a time.
Two branches means we must provide code for each function. I chose to disable start/end times when the All Day checkbox is selected (Listing 3). This is another print statement that affects components. The component is grayed out and cannot be modified by clicking on it.
[checkbox.alldaySet] 'Perform action for the checkbox named 'checkbox.allday'
print #main.statictext.starttime, "!disable"
print #main.combobox.starthour, "disable"
print #main.combobox.startminute, "disable"
'
print #main.statictext.endtime, "!disable"
print #main.combobox.endhour, "disable"
print #main.combobox.endminute, "disable"
wait
LISTING 3 – As shown here, I chose to disable start/end times when the All Day checkbox is selected. The component is grayed out and cannot be modified by clicking on it.
While print statements in Listing 3 may look alike, note that the statictext components have a preceding “!”. The “!” is used as an escape character here, because without the escape character, the command would simply print the text “!disable,” instead of altering the present component’s function. Disabling components just gives the user a bit of feedback as to what can and can’t be altered.
The remainder of the date/time components are all arrays of numbers. The array day
values 1-31 equals the array day
index 1-31, so it could use the index to retain the user choice. The array year
values 2021-2050 do not equal the array year
index 1-31, so we need to use the value, and not the index, to retain the user choice. This is also true with the four choices in the Show As combobox.
SAVE ME
Now that all the information has been collected for the simple event, let’s create the VCALENDAR text file. Clicking on the save button branches to the save routine. After ensuring that any “blank” text areas were intentionally left blank, we need to decide on a name for the file and determine where it will be stored. This is all handled via the file dialog. A pop-up allows you to enter a file name and choose the folder on your PC where it will be placed. If a legal file name is entered, we can open an output file and begin to save text to the file. Following the format of the VCALENDAR.ics file, we must begin with some standard lines of text that form the start of the VCALENDAR file. Two required properties of the VCALENDAR
component are VERSION
and PRODID
. Listing 4 shows and explains the VERSION/PRODID
code, and then lists and explains the event component from start to end.
print #myFile, "VCALENDAR:START"
print #myFile, "VERSION:2.0"
print #myFile, "PRODID://from the bench column #371//"
These print statements direct the text to be sent to the opened file. Each line of text is closed out with a carriage return (CR=0x0D) character. Now the event can be saved. An event begins with the mandatory event component requirement.
print #myFile, "VEVENT:START"
print #myFile, "UID:" + myDTSTAMP$ + "@fromthebench#372"
This is followed by a manufactured UID component. I’m identifying the manufacturer as the string “present time” plus a reference to this column. The present time must be formatted in UTC format: year, month, day, “T,” hour, minute, second and an optional time zone identifier, which I build by concatenating the appropriate items.
[getmyDTSTAMP] myDTSTAMP$ = str$(year)
if month<10 then myDTSTAMP$ = myDTSTAMP$ + "0"
myDTSTAMP$ = myDTSTAMP$ + str$(month)
if day<10 then myDTSTAMP$ = myDTSTAMP$ + "0"
myDTSTAMP$ = myDTSTAMP$ + str$(day) + "T"
if hour<10 then myDTSTAMP$ = myDTSTAMP$ + "0"
myDTSTAMP$ = myDTSTAMP$ + str$(hour)
if minute<10 then myDTSTAMP$ = myDTSTAMP$ + "0"
myDTSTAMP$ = myDTSTAMP$ + str$(minute) + "00"
return
Note that month, day, hour, minute and second must be two characters in length. MyDTSTAMP$ is used again as the timestamp for the creation of this event. Two additional UTC formatted date/time properties are added, indicating the start and end time of the event. Note that like the DTSTAMP, these come from similar routines that build that property’s UTC value.
print #myFile, "DTSTAMP:" + myDTSTAMP$
print #main.checkbox.allday, "value? temp$";
gosub [getmyDTSTART] print #myFile, "DTSTART:" + myDTSTART$
gosub [getmyDTEND] print #myFile, "DTEND:" + myDTEND$
end if
The rest of the properties are added depending on the present state of each of the remaining application components, “Show As,” “Summary,” “Location” and “Description.”
if showas$ = "busy" then print #myFile, "TRANSP:OPAQUE"
if showas$ = "free" then print #myFile, "TRANSP:TRANSPARENT"
if showas$ = "tentative" then print #myFile, "TRANSP:OPAQUE"
if showas$ = "out of the office" then print #myFile, "TRANSP:OPAQUE"
print #main.textbox.description, "!contents? temp$";
if temp$ <> "" then print #myFile, "DESCRIPTION:" + temp$
print #main.textbox.summary, "!contents? temp$";
if temp$ <> "" then print #myFile, "SUMMARY:" + temp$
print #main.textbox.location, "!contents? temp$";
if temp$ <> "" then print #myFile, "LOCATION:" + temp$
print #myFile, "VEVENT:END"
The VEVENT:END statement wraps up the event component. The VCALENDAR file is then completed with the VCALENDAR:END statement followed by the close file command.
print #myFile, "VCALENDAR:END"
close #myFile
LISTING 4 – The VERSION/PRODID code and creation of an event component from start to end
You can now find the complete calendar.ics file in the folder you chose. So, what can you do with it? The first example shown is a Picnic event that I host every year on the Fourth of July (Figure 3). While this first example is a one-time event, the application written for this column will add additional properties to indicate that this is a recurring event. When attached to an email sent to my family and friends, it could be added automatically to everyone’s calendar applications.
With my memory like a steel sieve, I just cannot remember all my family’s anniversary and birthday dates. I can use an application like this to add these annual reminders to my Calendar, and share it with others. I’m a bit embarrassed that I have trouble remembering the birth dates and ages of my sisters, my kids and grandkids. This app could be my ginkgo biloba.
EXPLORE IT
The .ics file format allows you to import, export and share Calendar entries with other users. As an international standard, the format enables many digital calendars—such as Microsoft Outlook and Google Calendar—to process the file’s components and properties.
You can expand this application to include multiple different events within the same .ics file and alarms, which might include AUDIO, DISPLAY or EMAIL notification. The standard is quite extensive. You can read more about the standard and its history by visiting icalendar.org. I’ve already continued to update this app. You can find my work files on the Circuit Cellar’s article code and files webpage. I think I need to replace the paper wall calendar with a display that’s kept up to date by linking all the individual family calendars. Hmm, too much to do, so little time.
RESOURCES
References:
[1] RFC2445 www.tools.ietf.org/html/rfc2445
[2] RFC5545 www.tools.ietf.org/html/rfc5545
iCalendar.org | www.icalendar.org
Liberty BASIC | www.libertybasic.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JUNE 2021 #371 – Get a PDF of the issue
Sponsor this ArticleJeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.