iCalendar

iCalendar (iCal) est un format de données (RFC 5545) pour les échanges de données de calendrier.

Il est implémenté/supporté par un grand nombre de logiciels (Google Agenda utilise également cette norme).

Extensions les plus répandues des fichiers (texte) au format iCalendar : .ics, .iCal ou .icalendar

Wikipédia : iCalendar

Structure d’un calendrier iCalendar :

  • La première ligne doit être BEGIN:VCALENDAR
  • le corps du calendrier : une suite de propriétés et un ou plusieurs composants de calendrier. Les composants du calendrier sont une collection de propriétés qui suivent une syntaxe particulière. Par exemple, le composant peut spécifier un événement, une tâche, …
  • la dernière doit être END:VCALENDAR

Exemple basique :

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
BEGIN:VEVENT
DTSTART:19970714T170000Z
DTEND:19970715T035900Z
SUMMARY:Fête à la Bastille
END:VEVENT
END:VCALENDAR

Chaque propriété est sur une ligne séparée commençant par un identificateur (formé de lettres, chiffres ou -). Chaque propriété comprend dans l’ordre son nom, un délimiteur : et sa valeur (ou plusieurs valeurs séparées par des virgules , dans un ordre non significatif). Les propriétés sont normalement non ordonnées.

Les propriétés peuvent comprendre un ou plusieurs paramètres facultatifs sous forme paramètre=valeur séparés par un point-virgule ;.

Remarque : Les lignes qui commencent par une espace forme la suite de la propriété commencée à la ligne précédente (les espaces en début de ligne et le saut de ligne qui précède sont ignorés) ; cette continuation des lignes est normalement imposée car le format impose une longueur maximale de 75 caractères par ligne (non compris les caractères de contrôles en fin de ligne). Il n’y a normalement aucune ligne vide, les espaces en fin de ligne sont non significatifs, de même que les espaces qui suivent une autre espace.

Un composant VEVENT offre un ensemble de propriétés qui décrivent un événement sur un calendrier.

Les propriétés classiques d’un composant VEVENT sont :

  • DTSTART : Date de début de l’événement
  • DTEND : Date de fin de l’événement
  • SUMMARY : Titre de l’événement
  • LOCATION : Lieu de l’événement
  • CATEGORIES : Catégorie de l’événement (ex: Conférence, Fête…)
  • STATUS : Statut de l’événement (TENTATIVE, CONFIRMED, CANCELLED)
  • DESCRIPTION : Description de l’événement

Exemple basique :

BEGIN:VEVENT
DTSTART:19970714T170000Z
DTEND:19970715T035900Z
SUMMARY:Fête à la Bastille
END:VEVENT

Exemple Qt

Code source : calendrier-qt.zip

QCalendarWidget

Qt fournit une classe QCalendarWidget qui est un widget de calendrier pour afficher et sélectionner des dates dans une GUI.

QCalendarWidget *wCalendrier = new QCalendarWidget(this);

wCalendrier->setMinimumDate(QDate(1900, 1, 1));
wCalendrier->setMaximumDate(QDate(3000, 1, 1));
wCalendrier->setGridVisible(true);

QTextCharFormat format;
format.setFontWeight(QFont::Bold);
wCalendrier->setHeaderTextFormat(format);

La classe QCalendarWidget fournit le signal clicked(QDate) avec en paramètre l’objet QDate sélectionné :

connect(wCalendrier, SIGNAL(clicked(QDate)), this, SLOT(selectionnerDate(QDate)));

void MainWindow::selectionnerDate(QDate date)
{
    qDebug() << __FUNCTION__ << date;
}

QNetworkAccessManager, QNetworkReply et QNetworkRequest

Récupération du calendrier .ics via une URL :

  • La requête :
QNetworkAccessManager networkManager;
QNetworkRequest requete;
QNetworkReply* networkReply;

QUrl hostURL("https://calendar.google.com/calendar/ical/u7m67cnmhd7b50q9qjndnus0ag%40group.calendar.google.com/public/basic.ics");

requete.setUrl(hostURL);
requete.setRawHeader("User-Agent", "CalendarClient_ICS");
requete.setRawHeader("Depth", "0");
requete.setRawHeader("Prefer", "return-minimal");
requete.setRawHeader("Content-Type", "text/xml; charset=utf-8");
requete.setRawHeader("Content-Length", "0");

QSslConfiguration configuration = requete.sslConfiguration();
configuration.setPeerVerifyMode(QSslSocket::VerifyNone);
requete.setSslConfiguration(configuration);

networkReply = networkManager.get(requete);

if (networkReply != nullptr)
{
  connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleHTTPError()));
  connect(networkReply, SIGNAL(finished()), this, SLOT(traiterRequete()));
}
  • Le traitement de la requête :
void Calendrier::traiterRequete()
{
    if (networkReply != nullptr)
    {
        QBuffer buffer(this);
        buffer.setData(networkReply->readAll());

        buffer.open(QIODevice::ReadOnly);
        bool trouve = false;
        do
        {
            QString ligne = buffer.readLine();
            if (!ligne.isNull())
            {
                if (ligne.startsWith("X-WR-CALNAME"))
                {
                    QString nomCalendrier = ligne.trimmed();
                    nomCalendrier.remove(";LANGUAGE=fr", Qt::CaseInsensitive);
                    nomCalendrier.remove("X-WR-CALNAME:", Qt::CaseInsensitive);
                    qDebug() << __FUNCTION__ << nom << nomCalendrier;
                    trouve = true;
                }
            }
        }
        while (!buffer.atEnd() && !trouve);
        buffer.seek(0);
        buffer.close();

        contenuCalendrier = new QTextStream(buffer.data());

        // ...
    }
}

Code source : calendrier-qt.zip

Voir aussi

Exemple Android

Code source : Calendrier.zip

CalendarView

Android fournit la classe CalendarView qui est un widget de calendrier pour afficher et sélectionner des dates dans une activité.

Lien : Calendar View Tutorial

Placer le widget CalendarView dans un layout :

...
<CalendarView
    android:id="@+id/calendarView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="2dp"
    android:layout_marginBottom="15dp" />
...

Gérer le widget CalendarView à partir d’une activité :

public class MainActivity extends AppCompatActivity
{
  private CalendarView calendarView = null;
  final int LUNDI = 2; // Premier jour de la semaine !

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      calendarView = (CalendarView) findViewById(R.id.calendarView);
      calendarView.setDate(System.currentTimeMillis(), false, true); // aujourd'hui
      calendarView.setFirstDayOfWeek(LUNDI); // Premier jour de la semaine
      calendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener()
      {
          @Override
          public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth)
          {
            Log.d(TAG, "onSelectedDayChange() selectedDate = " + dayOfMonth + "/" + (month + 1) + "/" + year);
          }
      });
  }
}

iCal4j

iCal4j est une bibliothèque Java utilisée pour lire et écrire des flux de données iCalendar comme défini dans la RFC2445.

Liens :

Il existe quelques dépendances de bibliothèque requises par iCal4j :

  • backport-util-concurrent:3.1
  • commons-codec:1.8
  • commons-lang:2.6

  • Modifier le fichier app/build.gradle pour intégrer les bibliothèques :

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    defaultConfig {
        applicationId "com.lasalle.calendrier"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'org.mnode.ical4j:ical4j:1.0.5'
    implementation 'backport-util-concurrent:backport-util-concurrent:3.1'
    implementation 'commons-codec:commons-codec:1.8'
    implementation 'commons-lang:commons-lang:2.6'

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

Avec iCal4j, on peut importer et analyser le contenu d’un calendrier de plusieurs manières :

// Parsing a calendar file
FileInputStream fin = new FileInputStream("mycalendar.ics");
CalendarBuilder builder = new CalendarBuilder();
Calendar calendar = builder.build(fin);

// Parsing a calendar string
String myCalendarString = ...
StringReader sin = new StringReader(myCalendarString);
CalendarBuilder builder = new CalendarBuilder();
Calendar calendar = builder.build(sin);

// À partir d'une URL
URL url = new URL("https://calendar.google.com/calendar/ical/u7m67cnmhd7b50q9qjndnus0ag%40group.calendar.google.com/public/basic.ics");
HttpsURLConnection connexionHTTPS = (HttpsURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(connexionHTTPS.getInputStream());
BufferedReader r = new BufferedReader(new InputStreamReader(in));
StringBuilder sb = new StringBuilder();
String ligne;
while(true)
{
    if (!((ligne = r.readLine()) != null))
        break;
    sb.append(ligne);
    sb.append("\r\n");
}
StringReader sin = new StringReader(contenuCalendrier);
calendar = builder.build(sin);

L’utilisation d’un calendrier via une URL nécessite un accès Internet.

Ajouter les permissions pour l’accès Internet dans le fichier AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lasalle.calendrier">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Calendrier">
        <activity android:name=".ParametresAgenda"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

La récupération d’un calendrier via une URL nécessitera aussi l’exécution du traitement dans une tâche d’arrière plan (thread).

On peut par exemple utiliser la classe AsyncTask en redéfinissant les méthodes :

  • doInBackground() : la seule méthode qui s’exécute dans un autre thread.
  • onPostExecute() : appelée lorsque la méthode doInBackground() est terminée.

Lire : Tâches d’arrière-plan [PDF]

Principe :

public class AsyncTaskHTTP extends AsyncTask<String, Void, String>
{
    public AsyncTaskHTTP()
    {
    }

    @Override
    protected String doInBackground(String... strings)
    {
      // Récupération du contenu du calendrier .ics
    }

    @Override
    protected void onPostExecute(String contenuCalendrier)
    {
      // Création du calendrier
    }
}

Le calendrier est composé d’une suite de propriétés et d’un ou plusieurs composants de calendrier :

// Le contenu du calendrier
Log.d(TAG, "Agenda = " + calendar);

// Quelques les propriétés du calendrier
Log.d(TAG, "ProductId = " + calendar.getProductId().toString());
Log.d(TAG, "Version = " + calendar.getVersion().toString());
Log.d(TAG, "CALNAME = " + calendar.getProperty("X-WR-CALNAME").getValue());

// Toutes les propriétés du calendrier
PropertyList plist = calendar.getProperties();
for (Object object : plist.toArray())
{
    Log.d(TAG, "objet = " + object);
}

Il est possible ensuite d’extraire les événements VEVENT.

Exemple pour un jour choisi :

TimeZone tz = TimeZone.getTimeZone("France/Paris");
java.util.Calendar dateDalendrier = new GregorianCalendar(annee,mois,jour,heure,minute,0);
dateDalendrier.setTimeZone(tz);

java.util.Calendar dateDalendrierFin = new GregorianCalendar(annee,mois,jour,23,59,59);
dateDalendrierFin.setTimeZone(tz);

// choix du format
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.FRENCH);
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String jourChoisi = sdf.format(dateDalendrier.getTime()); // à partir du timestamp

List evenements = null; // la liste des évènements du jour choisi
if (calendar != null && chargement)
{
    String jourFin = sdf.format(dateDalendrierFin.getTime()); // à partir du timestamp
    DateTime debut = null;
    DateTime fin = null;
    try
    {
        debut = new DateTime(dateDalendrier.getTime()); // le jour choisi
        fin = new DateTime(dateDalendrierFin.getTime().getTime());
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    // Définit la période d'une journée
    Period periode = new Period(debut, fin);

    // Crée un filtre pour cette période
    Filter filtre = new Filter(new PeriodRule(periode));

    // Filtre les évènements du jour choisi
    evenements = (List) filtre.filter(calendar.getComponents(Component.VEVENT));

    // Déboguage
    for (Object obj : evenements)
    {
        VEvent evenement = (VEvent) obj;

        // Toutes les propriétés de l'évènement
        for (Object o : event.getProperties())
        {
            Property property = (Property) o;
            Log.d(TAG, "(VEvent) Property [" + property.getName() + ", " + property.getValue() + "]");
        }
    }
}

Code source : Calendrier.zip

Retour au sommaire