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 :
BEGIN:VCALENDAR
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énementDTEND
: Date de fin de l’événementSUMMARY
: Titre de l’événementLOCATION
: Lieu de l’événementCATEGORIES
: 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énementExemple basique :
BEGIN:VEVENT
DTSTART:19970714T170000Z
DTEND:19970715T035900Z
SUMMARY:Fête à la Bastille
END:VEVENT
Code source : calendrier-qt.zip
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;
}
Récupération du calendrier .ics
via une URL :
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()));
}
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
Code source : Calendrier.zip
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 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