TDcal Logo   TDcal - ToDo Calendar
Opal Logo


TDcal is a desktop calendar and agenda client that supports the iCalendar and CalDAV standards.

TDcal is a set of command-line tools and a GUI program for editing calendar events as well as a synchronization daemon, a systray icon and a programming library.

TDcal is fully compliant with internet calendar standards (iCalendar, RFC5545) as well as the calendar sharing protocol (CalDAV, RFC4791) and also the authorization framework (OAuth, RFC6749).


TDcal supports the following features:


TDcal Screenshot TDcal Screenshot


Development of TDcal started in 2013 with additional work done in 2017, 2018, 2020, 2021 and 2022.

TDcal's user interface is modeled on that of the ical(1) program written by Sanjay Ghemawat in 1993 which the TDcal author used for 20 years before needing a standards-based calendaring tool.

TDcal maintains many similar user-interface features found in ical(1) but replaces the internal calendar storage with a standards-based one, and adds support for remote iCalendar downloads as well as server-based CalDAV calendar synchronization.

TDcal downloads all calendar data and stores it locally so that you have full access to your calendars while offline; changes are saved and synced back to servers when you are next online.

TDcal is implemented in Perl and uses the Gtk graphics libraries. It has been designed to be lightweight, fast and independent of window-manager. It does not use a database management system, instead storing all calendar data as files in a sub-directory of your home directory.

In addition to the graphical interface shown above, TDcal includes a command-line calendar manipulation tool as well as a notification daemon and a systray icon. The Perl programming API is also fully documented allowing additional applications to be developed.

The code for the GUI program tdg is now Gtk3-based using only non-deprecated features.


TDcal was developed on the FreeBSD operating system. It known to work on Linux too, and should work on any POSIX-compliant system. The GUI code requires a system supporting the Gtk3 libraries.


TDcal is not yet released. Please see below for how to request an early copy. Once the 1.0 version is out, the following instructions will apply...

TDcal can be installed from the ports system either as a binary package or by installing from the source. To install from binary package, do this:

    # pkg install tdcal

To deinstall:

    # pkg delete tdcal
To install from the port, do this:
    # cd /usr/ports
    # portsnap extract deskutils/tdcal
    # cd deskutils/tdcal
    # make
    # make install clean

To deinstall:

    # cd /usr/ports/deskutuls/tdcal
    # make deinstall

If you are installing without using the FreeBSD ports framework, you will need to manually add the following dependencies:

    # pkg install www/p5-Browser-Open
    # pkg install x11-toolkits/p5-Gtk3
    # pkg install net/p5-IO-Socket-INET6
    # pkg install security/p5-IO-Socket-SSL
    # pkg install converters/p5-JSON
    # pkg install devel/p5-Locale-gettext
    # pkg install net/p5-Net-Interface
    # pkg install textproc/p5-XML-Simple


TDcal is not yet released. Please see below for how to request an early copy. Once the 1.0 version is out, the following instructions will apply...

TDcal has been tested on Linux (specifically on MX Linux-21 and XUbuntu 21.04) and it works well.

Download the latest distribution file, below, and extract using:

    $ tar xvf tdcal-N.M.tar.xz

The Makefiles use FreeBSD make(1) syntax. They can be used on Linux using bmake(1).

Use the installation command:

    $ cd tdcal-N.M
    $ sudo PREFIX=/usr bmake install

The following package dependencies should be installed:


To deinstall:

    $ sudo PREFIX=/usr bmake deinstall

Getting Started

Quick Start

To start, a configuration file must be created which is done using any text editor. An example of a configuration file is shown below.

If you are configuring calendars on CalDAV services that require OAuth authorization, you will first have to go through a series of steps to register TDcal as an application that can access your account, obtain client credentials for it, add them to the configuration file, then run the tdoauth command to obtain an authorization code and access tokens in order to access your data on the server. See the tdoauth manual page for details of how to set things up.

Any of the tools can be run to check the syntax of the configuration file. Running the command-line tool td is the simplest way to see any error messages.

The notification and synchronization daemon tdd as well as the systray icon tdt are typically started at login or in the window-manager startup configuration and these are left running the whole time you are logged in.

The next step is to import calendar event data. If your calendar is already on a remote server, the tdd daemon will fetch the calendar events and store copies of them locally on your computer. If your calendar is in a file, you can import it using the td command-line tool. If you are migrating from ical(1) you will need to convert the events in ical's .calendar file to iCalendar .ics format and then import it using the td command-line tool. See below for a tool to help do this.

To edit events in TDcal, the tdg graphical interface is typically used. This displays your calendars in a window and allows navigation through the events, editing them and adding new ones. Also, the td command-line tool can be used to add, edit and delete events from a terminal window, and it can also be used from shell scripts.

All changes to your calendars are stored locally on your computer. The changes are synchronized back to their CalDAV servers on a periodic basis by the tdd daemon which you started earlier. If desired, both the event editors, td and tdg can be told to start an immediate synchronization run whenever an event is changed.


The first manual to read is the general overview in TDcal(7).

The configuration file syntax is documented in td(5).

OAuth set-up is documented in the tdoauth(1) manual page.

Each of the commands are documented in their manual pages: td(1), tdg(1), tdd(1) and tdt(1).

The Perl programming API is documented in the TDcal(3) manual page; there are additional man pages for each of the TDcal support libraries: TDcal::CalDAV(3), TDcal::Fetch(3), TDcal::OAuth(3), TDcal::cache(3), TDcal::const(3), TDcal::hkl(3), TDcal::http(3), TDcal::iCalendar(3), TDcal::lock(3), TDcal::misc(3), TDcal::nls(3), TDcal::timezone(3) and TDcal::version(3).

Configuration Example

To be useful, TDcal needs a configuration file that defines the calendars you wish to use. The configuration file also defines your preferred alarm/notification schedule, and calendar display preferences such as which day of the week is the first day and whether to split the weekend and display around the week.

The configuration file is named: ~/.td

This configuration file must be created using your favorite editor and should be mode 600 (because it contains passwords).

An example that configures a local private calendar, one shared on your own nextCloud server, your Google Calendar and a downloaded public holidays calendar, would look like this:

    # TDcal configuration file

    alerts  60 30 15 5 0
    lperiod 14
    logsize 1000000

    cal     Local
    color   #ffdd00

    cal     MyNextCloud
    color   #00a0fa
    user    username
    pass    yadda-yadda-yadda-yadda-yadda
    alerts  90 60 45 30 15 10 5 0

    cal     MyNextCloud/contact_birthdays
    adalrts 17:00 10:00

    oaprv   google
    cli_sec 123yadda456yadda789yadda

    cal     MyGoogleCalendar

    cal     US Holidays
    color   #04a004
    updfreq 10080

Full details of the configuration file syntax can be found in the manual page td(5).

Importing Calendar Data

If you have configured any calendars on remote servers, it is simply necessary to start the TDcal synchronization daemon tdd which can be done by running it from the command-line or by starting it from a login or window-manager startup script.

tdd fetches full calendars from the remote servers and then periodically synchronizes changed events back to the remote servers.

If you have calendar events in a local .ics file and wish to import these into a TDcal calendar, you can do this using the td command-line tool:

    td -c calendar -i file.ics

Once synchonized or imported, the calendar events can be viewed and manipulated using the tdg GUI interface or by the td command-line program which can also be used for scripted manipulations.

If you are migrating from ical(1) and you don't relish the idea of re-entering years (decades?) worth of calendar events, you could try the ical2icalendar(1) conversion tool offered here. This tool converts ical's .calendar data into standard .ics format. Use it something like this:

    perl ~/.calendar | td -c calendar -i -

This tool is not guaranteed to accurately convert everything: especially entries with complex repetition rules may not be properly converted. So, after use, you should go through all events and check their repetition rules are as desired.

TDcal Components

The TDcal package includes a synchronization daemon, a systray icon, a graphical editor interface, a command-line tool and a Perl API library.

The Synchronization and Notification Daemon, tdd

Once the configuration has been created, the tdd synchronization daemon should be started. This is usually accomplished by running it from your login or window-manager startup scripts. tdd will be running the whole time that you are logged in.

tdd connects to each calendar's server and performs synchronization uploads and/or downloads.

tdd also provides the pop-up event notification alerts.

The frequency of the synchronization runs as well as the timing of the alerts are all configured in the configuration file. Note that TDcal does not use alert times given in event VALARM properties; those are stored but ignored! You configure your own desired alert schedules both globally and on a per-calendar basis in the configuration file.

The Systray Icon, tdt

tdt provides a systray icon showing the calendar date.

The icon can be hovered over to see a pop-up showing forthcoming calendar events. Clicking on the icon opens the TDcal GUI program, tdg.

tdt is usually also started by running it from your login or window-manager startup scripts and leaving it running the whole time you are logged in.

The GUI Calendar Editor, tdg

TDcal's main interactive interface is its GUI program, tdg.

tdg provides a user-interface displaying a calendar, navigation buttons to change dates, and shows events for the day in an all-day panel and a 15-minute timeslot panel. Events can be edited, moved, resized, copied, moved to other days, deleted and even moved to other calendars from this interface.

The interface is intuitive to use, supporting Xorg mouse select/paste, ICCCM-style Ctrl-C/Ctrl-X/Ctrl-V copy/cut/paste, as well as its own Alt-C/Alt-X/Alt-V event manipulation copy/cut/paste.

Events can be moved using Shift-Button1 dragging and they can be resized using Alt-Button1 dragging.

All changes to events are done in the local copy of the calendar stored on your computer. Default behavior is that changes are synchronized to CalDAV servers on a periodic basis by the tdd synchronization daemon. tdg can be configured to send changes to servers immeidately if so desired.

The Command-Line Interface, td

The TDcal suite has a command-line tool, td, that is useful for manipulating calendars events by hand from the shell or from scripts.

td can import and export whole calendars in iCalendar .ics format, search for and display events and events can be extracted from the calendar into a file for manual or scripted editing and then put back into the calendar.

td has numerous options to control its behaviour. Run it with the -x flag for an explanation of the options and read its manual page for full details.

The OAuth Authorization Setup tool, tdoauth

Some cloud service providers require that the OAuth framework is used to authorize access to your private data stored on their service.

OAuth is a multi-stage authorization framework that is intended to be more secure than a simple username and password. It is, however, a good bit more complex.

Normally, developers of applications that wish to access private data on such cloud services would create an application client account on the service provider, register the application, obtain client credentials for the application, and then distribute the application containing those credentials ready for users to use. Then when you, the user, use the application, the service first requests that you approve the application's request to access your private data. After that, the application receives access tokens to access that data and it can then proceed to do so.

TDcal does fully support registration as an OAuth client application with OAuth service providers. However, the process of creating and maintaining an OAuth application project with each such service provider involves setting up the client account, registering the application, generating its credentials and then going through application validation and web site validation. Doing that, and repeating it all with each service provider that requires OAuth, is not something that the TDcal developer has any interest in nor the time for.

There is also the issue of it being unclear if open-source software such as this is supposed to distribute an OAuth client_id and a client_secret since, by doing so, anyone could copy those credientials and use them in another app and masquerade as TDcal.

Therefore, the steps to register the application and obtain client credentials are left to you, the user, to do for yourself!

See the notes for the various services, below, for how to proceed, and also read the tdoauth manual page for additional information and the parameters you will need to complete the registration.

Once you have your own application credentials for TDcal, these are put in your ~/.td configuration file.

Then, the OAuth authorization setup tool, tdoauth is run in order to go through the steps to grant access to your account.

All this is a bit of a hassle, yes. But it is arguably better this way because you have total control of access grants to your account and there is no worry at all about privacy because the TDcal developer isn't involved in accessing your account in any way at all.

The good thing is that it works well once you have gone though all the steps.

The Perl Programming API, TDcal

All the TDcal commands, as well as some additional tools in the source tree, make use of the Perl programming API library. Using the API, additional Perl programs can be written to interact with calendars and events using the TDcal model.

The library consists of the main library as well as several additional packages, such as TDcal/, TDcal/, TDcal/, etc, that implement specific functionality. Interested developers can start by reading the TDcal(3) manual page which then refers to additional manual pages for the other packages.

NLS Support

TDcal's tdg GUI as well as the tdd notifications support the use of NLS to display the program's messages in the user's native language. Anyone wishing to provide a translation is encouraged to do so.

The translation files are in the tdcal/po directory. Simply copy the TDcal.pot template to LL.po and edit it with translations of all messages. Also copy the help file to and translate that too. Then send them to the author at the email address below.

Use of UTF-8 encoding is strongly preferred and requested.

For more information, see the GNU gettext manual.

Repetition Rules, RRULE Support

TDcal is fully compliant with the RFC5545 RRULE standard and supports all BYxyz rules including BYSETPOS. Complex repetitions can be described using multiple BYxyz specifiers as well as BYSETPOS and will display correctly in the various TDcal tools.

It should be noted, however, that many other calendar programs and mobile applications do not have full RRULE support. Often they cannot handle multiple BYxyz specifiers at the same time or they do not handle BYSETPOS at all, or they approximate it. This can mean that events with complex repetition specifications display fine in TDcal but display on incorrect dates, or even not at all, on other calendar apps.

As an example, if you wish to pay a bill two weekdays before a specific day of the month, you would need a rule similar to this, which works if your bill's due date is the 23rd:

meaning the 2nd-last weekday between the 19th-22nd. This displays correctly in TDcal and also on several tested CalDAV servers. But, oddly, it displays as "the last weekday of the month" on the calendar app on the author's phone app so it appears there, incorrectly, anywhere between the 26th-31st, depending on the month. (And that's the stock calendar app from one of the major phone vendors.)

Similarly, a simpler rule for the 2nd-last weekday of the month:

which, again, displays correctly in TDcal and on the author's CalDAV server, but does not display at all on the calendar app on the author's phone! (Again, the stock calendar app from one of the major phone vendors.)

The point here is that one should avoid complex rules and rules that use BYSETPOS in particular, if you are concerned that events display correctly on other apps too.

Timezone names

It has been mentioned that, when creating new events and then editing the event using the Alt-E editor, the timezone name may not be as expected although the event does show at the correct time. Often, a neighboring country's zone name is shown instead of the one expected.

This is because TDcal has to match the system's installed timezone definition with those in the zoneinfo database to determine the timezone names. On FreeBSD and Linux systems, the system timezone information (the zoneinfo database) is imported from Olson timezone sources. During 2022, the maintainers of the Olson timezone sources updated many zone definitions that share a common set of rules with the definition being given for one zone which is installed first. The other common zones are then installed by copying the first. (In the Olson sources, the subsequent zones have ‘Link’ directives.) When attempting to determine the zone name, TDcal's library searches the zoneinfo database for the first file (in directory order) matching the installed zone file in /etc/localtime and it returns the name of first matched file found.

E.g., as of late 2022, the following zones are copies:

The name of all three zones will display as the first.

Note that the directory order is not necessarily the first file installed. On UFS filesystems it likely will be. But on ZFS filesystems, directory ordering is based on a name hash algorithm. So the tzdata zone definition for the following:

actually returns Indian/Christmas for any of the other three when on a ZFS filesystem.

Note that your zone names may be fine now, but may change after updating the system zoneinfo database because the new version links zones together wheras the older version did not.

It is important that your timezone name reflects the closest country or region applicable to you. Your timezone may share a common definition with a neighboring country now but this could change in the future, for example as governments update daylight savings time rules.

If you need to override the timezone name, the good news is that the TDcal library does also know about the file /etc/timezone so if the name that is returned is not as you wish, you can override it by specifying a name in this file. E.g.:

    echo Europe/Amsterdam >/etc/timezone
    echo Asia/Bangkok >/etc/timezone
Remember then, that if you travel and change timezones, you will have to update both the /etc/localtime file and the /etc/timezone file. The script may be of use to you when updating your system's timezone files after travelling.

The timezone can also be overridden by setting the environment variable TZ or by defining a variable TZ= in /etc/TIMEZONE.

Versions of TDcal prior to 0.98 used to use the CPAN DateTime::TimeZone library. It does the same lookup and it also allowed the zone name to be overridden in /etc/timezone. However in late 2022, this library was itself updated to validate timezone names and is incorrectly returning the first installed zoneinfo name even when it is overriden in /etc/timezone.

Notes on Various CalDAV Servers

TDcal is actively used and tested with calendars on the following CalDAV servers. These notes are excerpted from the td(5) manual page:

Works well.
Calendars containing non-ASCII characters in their display names such as ‘My Baïkal Calendar’ are likely to be displayed like ‘My Ba�kal Calendar’ due to the server substituting such characters with the UTF-8 Replacement Character. This is a bug in feature of Baïkal and not a problem with TDcal.
Works well.
You must log on to the web site and create an application-specific password.
Works well.
Google requires the use of the OAuth authorization framework in order to access private data in your account.

On Google, to register the application and obtain application client credentials, this involves creating a project on your Google Cloud Console, enabling the CalDAV API for the project, creating an OAuth client registration with access scope to your calendar, and obtaining client credentials which you then put into your ~/.td configuration file. Since you are registering a client app for your own use, you do not need to go through the steps of client validation or website validation. After storing your client credentials, you will need to run tdoauth to actually grant authorization to TDcal to access your calendar data. Read the tdoauth manual page for details of the parameters you will need for registering the app and obtaining client credentials.

You can control which of your Google calendars are enabled for syncing using CalDAV by logging on to your Google account and then visiting this URL:
Works well.
If 2-factor-authentication is enabled, you must log on to the web site and create an application-specific password.

nextCloud v22 implemented a calendar trashbin feature that retains deleted events in a trashbin for a default period of 30 days. A bug in this feature causes nextCloud to reject an update to an event in the trashbin. This can cause Alt-X/Alt-V moves to fail if the Alt-X deletion was sent to the server before the Alt-V update happened. The error message in .td.log is:

    http error 400 on PUT URL: Deleted calendar object with uid already exists in this calendar collection.
As a work-around to avoid this, the nextCloud calendar trashbin feature can be disabled by the nextCloud administrator by running this command:
    php occ config:app:set dav calendarRetentionObligation --value=0
A problem report exists (nextCloud issue #30096) for nextCloud to change the trashbin behavior so that CalDAV updates to deleted events restore the event from the trashbin rather than failing.
Works well.
If 2-factor-authentication is enabled, you must log on to the web site and create an application-specific password.
Works well.
You must log on to the web site and create an application-specific password.
Yahoo! Calendar's CalDAV implementation has some significant unusual aspects:
  • URL percent-encoding is incorrectly done twice, resulting in a calendar called ‘My Calendar’ being sent as .../My%2520Calendar/... instead of simply .../My%20Calendar/.... TDcal has work-around code to detect and correct for this problem in both calendar and event URLs.
  • Yahoo! Calendar also rejects events with repetition frequencies of ‘FREQ=HOURLY’ or smaller, returning a ‘500 Server Error’ when such events are uploaded. Repeating events only work with ‘FREQ=DAILY’ or larger.
  • Yahoo! Calendar also adds several iCalendar records to all events uploaded to the server. These additions mean that an entity tag for the new event is not immediately available on upload. The event must be redownloaded in order to obtain the changed version and its entity tag. This happens automatically for you, but it is an additional use of network bandwidth.

Source Distributions and Version History

Bug Tracker

Bug reports and feature requests can be submitted on the Opal Bug Tracker or sent by email to the developer at

Git server

TDcal is not yet released. Please see below for how to request an early copy. Once the 1.0 version is out, the following instructions will apply...

The lastest development version of the source can be downloaded from the Opal Git server:


Tarballs and Changelog


tdcal-1.0.tar.xz ... coming soon
First publicly-released version.


2021 Aug 05
TDcal is sufficiently stable for daily use and is known to run on FreeBSD and on Linux. It has been tested against the various CalDAV servers listed on this page.

The current version is tdcal-0.119.tar.xz 2024/01/30 16:54 UTC.

If you are interested in giving it an early try-out and are willing to send feedback, please contact the developer by email at Thanks!

Ongoing development work:
Testing and further bug fixing:

Ongoing development work:
Testing and further bug fixing:

Ongoing development work:
Testing and further bug fixing:

Ongoing development work:
Much testing and further bug fixing:

Ongoing development work:

Ongoing development work:

Development resumed after gap of four years!

Initial development of the basic libraries and tools:
Basic functionality working and proven.

Contacting the Author and Port Maintainer

If you have comments or suggestions for changes please get in touch with the developer at