Compare commits

...

78 Commits

Author SHA1 Message Date
4313a7ef84 updated readme 2024-11-23 15:40:51 +01:00
3bf6fea43d removed uncommented code, updated readme 2024-11-23 15:30:11 +01:00
8e4c37fe88 all unit tests successful, might have fixed bug 2024-11-23 12:24:43 +01:00
2e60c19b0a removed Makefile 2024-11-23 08:50:08 +01:00
ed4d5cc798 install bin with meson 2024-11-17 17:35:52 +01:00
a7d2a49931 added TODO 2024-11-17 10:13:33 +01:00
7bef5ea855 all unit tests migrated to meson 2024-11-17 10:10:52 +01:00
28d6710c0a second unit test works with meson and found source of bug 2024-11-17 10:04:29 +01:00
f69d56d91a first unit test runs with meson 2024-11-17 09:51:05 +01:00
8bdb19d0c0 included failed ics file in unit tests 2024-11-17 09:39:03 +01:00
58e0c8feb3 meson build works and is simple 2024-11-17 09:26:32 +01:00
750d450b75 changed date in test file 2024-11-16 16:35:19 +01:00
0eda58f63f added test file and TODOs 2024-11-11 07:34:17 +01:00
ad96144904 updated readme 2024-10-05 16:05:21 +02:00
7e36e9143d annotated print_upcoming function 2024-10-05 15:56:08 +02:00
773bd82216 tests are looking good for ongoing events 2024-10-05 15:47:47 +02:00
c20f1ca1f0 renamed function parameter 2024-07-10 16:50:24 +02:00
578bf0b0f5 fixed pretty_print_date_time 2024-07-10 16:42:43 +02:00
33d9fed0cf added unit-test for print_upcoming() 2024-07-10 15:25:26 +02:00
9bba31490b added pragma once to header file 2024-07-09 11:17:34 +02:00
2c6f4bb357 renamed unit test
only test one function per unit test file.
2024-07-09 08:49:48 +02:00
2442af328b improved README 2024-07-09 08:40:13 +02:00
37b6e1ce2c removed unneeded includes in main.c 2024-07-09 08:32:32 +02:00
3ead0c07b2 added Makefile for unit test 2024-07-09 08:27:18 +02:00
c4fc332419 added first unit test 2024-07-09 08:08:01 +02:00
ad344b03ac commented function, closed file 2024-07-09 07:13:01 +02:00
7809fb64ab implemented new function parse_ics_file 2024-07-09 07:02:12 +02:00
d225e6aa41 updated README, how to enable git-hooks 2024-07-08 20:42:24 +02:00
5a96c95f69 changed ctags command 2024-07-08 20:39:44 +02:00
f2015b062c added git-hooks dir 2024-07-08 20:25:54 +02:00
a8006f2e28 Update README.md 2024-06-16 08:26:31 +02:00
7fc1a17d3b only go back one character to include the new line before field 2024-03-03 17:05:08 +01:00
f248a1d279 Merge pull request 'unrecognized-event-bug' (#5) from unrecognized-event-bug into master
Reviewed-on: bjoernf/ics_analyzer#5
2024-03-03 16:40:56 +01:00
97208505d8 fixed bug and created script for running tests 2024-03-03 16:29:57 +01:00
75cacc3e04 redacted test file 2024-03-03 16:10:12 +01:00
0c1e64a16e added test file 2024-03-03 16:05:23 +01:00
31b8c83d2f updated README 2024-01-20 21:58:28 +01:00
c786ccf659 Merge pull request 'unfolding-function' (#4) from unfolding-function into master
Reviewed-on: bjoernf/ics_analyzer#4
2024-01-20 21:54:55 +01:00
76c7f414ef changed formatting 2024-01-20 21:51:07 +01:00
3252bf67ae corrected comment 2024-01-20 21:06:09 +01:00
ff20bb92f7 dont put field name summary into summary field 2024-01-20 21:00:18 +01:00
7a5b3bc97a added space folded test case and improved unfolding function 2024-01-20 20:44:15 +01:00
b2a2d09a92 improved folding function 2024-01-20 20:38:23 +01:00
f874fb99a6 still messy 2024-01-20 20:28:28 +01:00
ecfae506c9 unfolding doesnt work correctly 2024-01-20 19:19:39 +01:00
ce356065a7 added tab folded test case 2024-01-04 07:04:39 +01:00
05d59f6697 updated docs 2024-01-03 18:06:43 +01:00
5b31aec8d6 updated todo 2024-01-03 17:59:59 +01:00
bf6fdd71eb improved documentation 2024-01-03 17:57:25 +01:00
6af368a69c added carriage returns to test file 2024-01-03 08:14:15 +01:00
ea72a21418 line breaks are CRLF 2024-01-03 08:06:26 +01:00
55c6a39ee8 added rfc url to docs 2024-01-03 07:58:55 +01:00
add437c484 renamed test file 2024-01-03 07:56:57 +01:00
0e85ea09cd new test file 2024-01-03 07:26:49 +01:00
af46177bcd fixed bug when SUMMARY is not directly followed by colon 2024-01-01 10:15:32 +01:00
4ff2e705e2 modified test case 2023-12-30 06:00:45 +01:00
f9a617a882 location may be null 2023-12-30 05:17:58 +01:00
bf6d09e99a added asserts to prevent segfaults from dereferencing null pointers 2023-12-30 05:02:49 +01:00
78a07b128f improved Makefile and use debugging flags 2023-12-29 06:30:52 +01:00
315d127717 Merge pull request 'cap-event-printing' (#3) from cap-event-printing into master
Reviewed-on: bjoernf/ics_analyzer#3
2023-09-17 10:36:31 +02:00
925d9b35ce updated readme 2023-09-17 10:33:37 +02:00
167457b791 added new all day event to test ics file 2023-09-17 10:32:16 +02:00
e8ed8b0319 default to printing 5 events and added option -a 2023-09-17 10:27:52 +02:00
0773d4f561 removed unused move_lines.c 2023-09-16 12:39:49 +02:00
0955921918 checked out changes from fix-location-field-bug branch 2023-09-16 12:02:22 +02:00
c484acb88e checked out .gitignore from fix-location-field-bug 2023-09-16 11:48:13 +02:00
0206625a76 checked out readme from fix-location-field-bug 2023-09-16 11:46:52 +02:00
0cd92b929d fix-end-date-printing (#2)
printing of the end date was fixed

Reviewed-on: bjoernf/ics_analyzer#2
Co-authored-by: bjoernf <bjoern.foersterling@web.de>
Co-committed-by: bjoernf <bjoern.foersterling@web.de>
2023-09-10 11:37:32 +02:00
061ed30333 insert_event (#1)
Option to insert events seems to work.

Reviewed-on: bjoernf/ics_analyzer#1
Co-authored-by: bjoernf <bjoern.foersterling@web.de>
Co-committed-by: bjoernf <bjoern.foersterling@web.de>
2023-09-09 08:26:50 +02:00
c713522736 updated readme 2023-08-30 18:05:48 +02:00
47a7d134ed updated readme 2023-08-26 09:58:23 +02:00
b3d7d6b45b added Makefile inside src dir 2023-08-25 10:44:15 +02:00
3b348066bd updated readme 2023-08-24 14:37:56 +02:00
bdface378e added newline between events 2023-08-24 02:15:46 +02:00
1d0e412d86 updated readme 2023-08-24 02:10:44 +02:00
455d60f47f updated readme 2023-08-24 01:57:34 +02:00
d8cb527f61 added end_date and function to print it accordingly 2023-08-24 01:55:50 +02:00
3a53493e74 changed member date to start_date 2023-08-24 01:10:53 +02:00
36 changed files with 1343 additions and 337 deletions

5
.gitignore vendored
View File

@ -1,2 +1,5 @@
a.out icscli
.gitconfig .gitconfig
*.o
*.out
tags

View File

@ -1,29 +1,57 @@
Show upcoming events of an .ics file. ## ics_cli
#### use case
- show upcoming events of an .ics file
- insert events in your calendar
```
make run
```
#### installation #### installation
``` ```
make cd src/builddir
``` ```
``` ```
sudo make install meson compile
``` ```
```
meson install
```
#### usage
the default path is for evolution ics files at `~/.local/share/evolution/calendar/system/calendar.ics`
```
icscli
```
for a custom path
```
icscli -f path/to/ics/file.ics
```
```
icscli -h
```
#### uninstall #### uninstall
``` ```
sudo make uninstall sudo rm -f /usr/local/bin/icscli
``` ```
#### git-hooks for ctags pre-commit
The developer has to actively enable git hooks:
```
git config --local core.hooksPath git-hooks
```
This will run `ctags` on every commit.
#### TODO #### TODO
- change member date to start_date - improve and automate unit testing
- include member end_date - add cli argument that will not show ongoing events, only upcoming events
- use strftime function in pretty_print_date_time function?
- improve makefile
- tests

79
docs/ics_format.txt Normal file
View File

@ -0,0 +1,79 @@
RFC5545
-------
https://www.rfc-editor.org/rfc/rfc5545
The iCalendar object is organized into individual lines of text,
called content lines. Content lines are delimited by a line break,
which is a CRLF sequence (CR character followed by LF character).
Lines of text SHOULD NOT be longer than 75 octets, excluding the line
break. Long content lines SHOULD be split into a multiple line
representations using a line "folding" technique. That is, a long
line can be split between any two characters by inserting a CRLF
immediately followed by a single linear white-space character (i.e.,
SPACE or HTAB). Any sequence of CRLF followed immediately by a
single linear white-space character is ignored (i.e., removed) when
processing the content type.
For example, the line:
DESCRIPTION:This is a long description that exists on a long line.
Can be represented as:
DESCRIPTION:This is a lo
ng description
that exists on a long line.
The process of moving from this folded multiple-line representation
to its single-line representation is called "unfolding". Unfolding
is accomplished by removing the CRLF and the linear white-space
character that immediately follows.
When parsing a content line, folded lines MUST first be unfolded
according to the unfolding procedure described above.
--------------------------------------------------------------------------------
Colons seem to be allowed in the "value" so you can't split by colon.
You have to split by "\r\n" but unfold the lines before.
Observation of different ics files from different sources
---------------------------------------------------------
ICS files from Microsoft Outlook differ from those that were created by Gnome Evolution.
The order of "fields" seems not be standardized and differs between every application.
Evolution
---------
all line endings are \r\n
all day event:
DTSTART;VALUE=DATE:20230908
DTEND;VALUE=DATE:20230909
an all day event can span over multiple days and look like this:
DTSTART;VALUE=DATE:20230911^M$
DTEND;VALUE=DATE:20230916^M$
The DTEND of an all day event is exclusively, it is not an all day event anymore!
appointment:
DTSTART;TZID=/freeassociation.sourceforge.net/Continent/City:
20230909T090000
DTEND;TZID=/freeassociation.sourceforge.net/Continent/City:
20230909T093000
appointments can also span over multiple days:
DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230913T230000
DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230914T040000
SEQUENCE is the number of times the event was modified.
Evolution sets this to 2 after it has been generated.
Appointments include the continent and city of the used time zone.
"DESCRIPTION:" is an optional field.

29
git-hooks/pre-commit Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
# make a dir called git-hooks in the top level of your repo
# put the following line in a Readme.md:
# git config --local core.hooksPath git-hooks
# the user has to actively enable git hooks
# this script has to be named "pre-commit" and should be placed
# in the core.hooksPath
if type ctags &>> /dev/null; then
ctags_installed=1
else
ctags_installed=0
fi
if ! (( ${ctags_installed} )); then
printf "Ctags is not installed! Git hooks are enabled and can only be executed if it is installed.\n"
printf "Commit was aborted.\n"
exit 1
fi
if ! ctags -R src/.; then
printf "Ctags failed.\n\n"
exit 1
else
printf "Ctags was successfully executed.\n"
fi
exit 0

View File

@ -1,32 +0,0 @@
all:
gcc -Wall ./src/*.c
.PHONY: debug
debug:
gcc -Wall -g ./src/*.c
gdb a.out
.PHONY: run
run:
gcc -Wall ./src/*.c
./a.out
.PHONY: install
install: a.out
cp a.out /usr/local/bin/ics_analyzer
.PHONY: clean
clean:
rm a.out
.PHONY: uninstall
uninstall:
rm /usr/local/bin/ics_analyzer
.PHONY: test
test:
./a.out
@echo
./a.out -h
@echo
./a.out -f tests/calendar.ics

View File

@ -1,3 +1,4 @@
#include "insert_event.h"
#include "cli_arg_parsing.h" #include "cli_arg_parsing.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -5,37 +6,42 @@
#include <string.h> #include <string.h>
void usage() { void usage() {
printf ("-h\tprint this help\n"); printf ("-a\t\t\tshow all upcoming events (default is 5 events)\n");
printf ("-f\tspecify ics file path\n"); printf ("-f [FILE PATH]\t\tspecify ics file path\n");
exit(0); printf ("-h\t\t\tprint this help\n");
printf ("-i\t\t\tinsert an event\n");
exit(0);
} }
void get_cli_args(int argc, char **argv, char **file_name) { void get_cli_args(int argc, char **argv, char **file_name, int *show_all_events) {
int opt = 0; int opt = 0;
memset(file_name, '\0', strlen(*file_name)); memset(file_name, '\0', strlen(*file_name));
if (argc < 2) { char *home = getenv("HOME");
char *home = getenv("HOME"); *file_name = home;
*file_name = home;
if (home != NULL) { if (home != NULL) {
strcat(*file_name, "/.local/share/evolution/calendar/system/calendar.ics"); strcat(*file_name, "/.local/share/evolution/calendar/system/calendar.ics");
} else { } else {
printf ("Environment variable HOME is not set.\n"); printf ("Environment variable HOME is not set.\n");
exit(1); exit(1);
} }
return;
}
while ((opt = getopt(argc, argv, "f:h")) != -1) { while ((opt = getopt(argc, argv, "f:ahi")) != -1) {
switch(opt) { switch(opt) {
case 'f': case 'a':
*file_name = optarg; *show_all_events = 1;
break; break;
case 'h': case 'f':
usage(); *file_name = optarg;
} break;
} case 'h':
usage();
break;
case 'i':
insert_event(*file_name);
break;
}
}
} }

View File

@ -2,4 +2,4 @@
void usage(); void usage();
void get_cli_args(int argc, char **argv, char **file_name); void get_cli_args(int argc, char **argv, char **file_name, int *show_all_events);

View File

@ -1,41 +0,0 @@
// cut a string into two parts by the first occurence of delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
// cut a string into two parts by delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
#include <string.h>
void cut_string(char my_string[], char delimiter, int side) {
char part1[256] = "";
char part2[256] = "";
int split = 0;
int j = 0;
for (int i = 0; i < strlen(my_string); i++) {
if (my_string[i] == delimiter) {
if (split == 0) {
split = 1;
continue;
}
}
if (split == 0) {
part1[i] = my_string[i];
} else {
part2[j] = my_string[i];
j++;
}
}
memset(my_string, '\0', strlen(my_string));
if (side == 0) {
strcpy(my_string, part1);
} else {
strcpy(my_string, part2);
}
}

View File

@ -1,5 +0,0 @@
#pragma once
// cut string into two parts and choose one part
// side is 0 or 1
void cut_string(char my_string[], char delimiter, int side);

View File

@ -1,26 +1,182 @@
#include "date_time_handling.h" #include "date_time_handling.h"
#include "string_handling.h"
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>
void get_date(char buffer[]) { // buffer needs to contain a string with a strlen of 15 (format: "xxxxxxxxTxxxxxx")
// or a strlen of 16 (format: "YYYYmmddTHHMMSSZ")
void get_date (char buffer[]) {
// add 1 because strlen does not include the null character // add 1 because strlen does not include the null character
size_t buffer_size = strlen(buffer) + 1; size_t buffer_size = strlen (buffer) + 1;
time_t my_unix_ts = time(NULL); time_t my_unix_ts = time (NULL);
struct tm* my_tm_local = localtime(&my_unix_ts); struct tm* my_tm_local = localtime (&my_unix_ts);
strftime(buffer, buffer_size, "%Y%m%dT%H%M%S", my_tm_local); if (strlen (buffer) == 15) {
strftime (buffer, buffer_size, "%Y%m%dT%H%M%S", my_tm_local);
}
else if (strlen (buffer) == 16) {
strftime (buffer, buffer_size, "%Y%m%dT%H%M%SZ", my_tm_local);
}
} }
// 20230823T194138 -> 2023-08-23 19:41:38 // 20230823T194138 -> 2023-08-23 19:41:38
void pretty_print_date_time(char date_time[]) { // 20230823T194138Z -> 2023-08-23 19:41:38
char *date = strtok(date_time, "T"); // 20241209 -> 2024-12-09
char *time = strtok(NULL, "T"); // caller has to free() the returned char array
printf ("%c%c%c%c-", date[0], date[1], date[2], date[3]); char* pretty_date_time (char date_time[]) {
printf ("%c%c-", date[4], date[5]); // need one more char in the char pointer for null-termination
printf ("%c%c", date[6], date[7]); int pdt_len = 20;
if (time != NULL) { if (!strchr (date_time, 'T')) {
printf (" %c%c:", time[0], time[1]); pdt_len = 11;
printf ("%c%c:", time[2], time[3]); }
printf ("%c%c", time[4], time[5]);
char* pretty_dt = malloc (pdt_len);
memset(pretty_dt, '\0', pdt_len);
int dt_counter = 0;
if (!strcmp(date_time, "")) {
return pretty_dt;
}
for (int i = 0; i < (pdt_len-1); i++) {
if (i == 4 || i == 7) {
pretty_dt[i] = '-';
continue;
}
if (i == 13 || i == 16) {
pretty_dt[i] = ':';
continue;
}
if (date_time[dt_counter] == 'T') {
pretty_dt[i] = ' ';
} else {
pretty_dt[i] = date_time[dt_counter];
}
dt_counter++;
}
// null-terminate string
pretty_dt[strlen(pretty_dt)] = '\0';
return pretty_dt;
}
void pretty_print_date_time (char date_time[]) {
char* pdt = pretty_date_time(date_time);
printf("%s", pdt);
free(pdt);
}
void marshall_date_time (char date_time[]) {
char transformed_string[strlen (date_time)];
int j = 0;
remove_nl_and_cr (date_time);
for (int i = 0; i <= strlen (date_time); i++) {
if (date_time[i] != ':' && date_time[i] != '-') {
if (date_time[i] == ' ') {
transformed_string[j] = 'T';
}
else {
transformed_string[j] = date_time[i];
}
j++;
}
}
memset (date_time, '\0', strlen (date_time));
strcpy (date_time, transformed_string);
}
void print_end_date (char end_date[], char start_date[]) {
time_t start_uts = transform_date_to_unix_ts (start_date);
time_t end_uts = transform_date_to_unix_ts (end_date);
double time_difference = difftime (end_uts, start_uts);
// end_date is all day event
if (strlen (end_date) == 8) {
if (time_difference == 86400) {
return;
}
else {
printf (" - ");
end_uts -= 86400;
char* end_date_minus_one =
transform_unix_ts_to_date (end_uts);
pretty_print_date_time (end_date_minus_one);
free (end_date_minus_one);
return;
}
}
else {
// end_date is not an all day event
char* end_date_chunk = strtok (end_date, "T");
char* end_time_chunk = strtok (NULL, "T");
char* start_date_chunk = strtok (start_date, "T");
printf (" - ");
// only print the end date if it is not the same as the start date
if (strcmp (start_date_chunk, end_date_chunk) != 0) {
printf ("%c%c%c%c-", end_date_chunk[0],
end_date_chunk[1], end_date_chunk[2],
end_date_chunk[3]);
printf ("%c%c-", end_date_chunk[4],
end_date_chunk[5]);
printf ("%c%c ", end_date_chunk[6],
end_date_chunk[7]);
}
printf ("%c%c:", end_time_chunk[0], end_time_chunk[1]);
printf ("%c%c:", end_time_chunk[2], end_time_chunk[3]);
printf ("%c%c", end_time_chunk[4], end_time_chunk[5]);
} }
} }
// YYYYmmdd -> unix timestamp
time_t transform_date_to_unix_ts (char date_str[]) {
time_t unix_stamp = 0;
int date_only = atoi (date_str);
struct tm my_tm = { 0 };
my_tm.tm_year = date_only / 10000 - 1900;
my_tm.tm_mon = (date_only % 10000) / 100 - 1;
my_tm.tm_mday = date_only % 100;
unix_stamp = mktime (&my_tm);
return unix_stamp;
}
// unix timestamp -> YYYYmmdd
// make sure to free the returned buffer
char* transform_unix_ts_to_date (time_t unix_ts) {
char* date_buffer = malloc (9);
struct tm* my_tm;
my_tm = localtime (&unix_ts);
strftime (date_buffer, 9, "%Y%m%d", my_tm);
return date_buffer;
}
char* get_tz () {
char* timezone_path = malloc (256);
ssize_t bytes_read = readlink ("/etc/localtime", timezone_path, 255);
if (bytes_read != -1) {
// Null-terminate the string
timezone_path[bytes_read] = '\0';
}
else {
perror ("readlink");
exit (1);
}
return timezone_path;
}

View File

@ -1,5 +1,13 @@
#pragma once
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
void get_date(char buffer[]); void get_date(char buffer[]);
char *get_tz();
char* pretty_date_time(char date_time[]);
void pretty_print_date_time(char date_time[]); void pretty_print_date_time(char date_time[]);
void marshall_date_time(char date_time[]);
void print_end_date(char end_date[], char start_date[]);
time_t transform_date_to_unix_ts(char date_str[]);
char *transform_unix_ts_to_date(time_t unix_ts);

187
src/insert_event.c Normal file
View File

@ -0,0 +1,187 @@
#include "insert_event.h"
#include "string_handling.h"
#include "date_time_handling.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <uuid/uuid.h>
void insert_event(char *file_name) {
int myfd = open(file_name, O_RDWR);
int all_day_event = 0;
char summary_buf[256] = "SUMMARY:";
char *input_buffer = &summary_buf[8];
uuid_t uuid;
char uuid_str[37] = "";
char dtstamp[] = "YYYYmmddTHHMMSSZ";
char *time_zone = get_tz();
char *dtstart_buffer = malloc(128);
char *dtend_buffer = malloc(128);
remove_until_delim(time_zone, '/', 4);
printf("Insert a new event\n");
printf("Is this an all day event? [y/n] ");
all_day_event = binary_user_choice();
printf("SUMMARY: ");
fgets (input_buffer, (sizeof(summary_buf)-strlen(summary_buf)), stdin);
if (strchr(input_buffer, '\n') == NULL)
printf ("The input has been truncated to:\n%s\n", input_buffer);
// modify the string to have \r\n line ending
summary_buf[strlen(summary_buf)+1] = '\0';
summary_buf[strlen(summary_buf)-1] = '\r';
summary_buf[strlen(summary_buf)] = '\n';
uuid_generate(uuid);
// parse uuid to a string
uuid_unparse(uuid, uuid_str);
get_date(dtstamp);
get_dtstart_dtend(dtstart_buffer, all_day_event, "start");
marshall_date_time(dtstart_buffer);
form_dtstart_string(dtstart_buffer, time_zone);
get_dtstart_dtend(dtend_buffer, all_day_event, "end");
marshall_date_time(dtend_buffer);
form_dtend_string(dtend_buffer, time_zone);
seek_cal_end(myfd);
write(myfd, "BEGIN:VEVENT\r\n", strlen("BEGIN:VEVENT\r\n"));
write(myfd, "UID:", strlen("UID:"));
write(myfd, uuid_str, strlen(uuid_str));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "DTSTAMP:", strlen("DTSTAMP:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, dtstart_buffer, strlen(dtstart_buffer));
write(myfd, dtend_buffer, strlen(dtend_buffer));
write(myfd, "SEQUENCE:2\r\n", strlen("SEQUENCE:2\r\n"));
write(myfd, summary_buf, strlen(summary_buf));
write(myfd, "TRANSP:OPAQUE\r\n", strlen("TRANSP:OPAQUE\r\n"));
write(myfd, "CLASS:PUBLIC\r\n", strlen("CLASS:PUBLIC\r\n"));
write(myfd, "CREATED:", strlen("CREATED:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "LAST-MODIFIED:", strlen("LAST-MODIFIED:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "END:VEVENT\r\n", strlen("END:VEVENT\r\n"));
write(myfd, "END:VCALENDAR\r\n", strlen("END:VCALENDAR\r\n"));
close(myfd);
free(time_zone);
free(dtstart_buffer);
free(dtend_buffer);
exit(0);
}
void seek_cal_end(int fd) {
char search_string[] = "END:VCALENDAR";
int j = 0;
char char_reader = '\0';
lseek(fd, -1, SEEK_END);
while(read(fd, &char_reader, 1)) {
// no need to compare to the null terminator of the search_string
if (char_reader == search_string[strlen(search_string)-j-1]) {
j++;
} else {
j = 0;
}
if (j == (strlen(search_string))) {
lseek(fd, -1, SEEK_CUR);
break;
}
lseek(fd, -2, SEEK_CUR);
}
}
int binary_user_choice() {
char input_buffer[64] = "";
if (fgets (input_buffer, sizeof(input_buffer), stdin) == NULL) {
printf ("an fgets error occured\n");
}
if (! strchr(input_buffer, '\n')) {
printf ("Input buffer overflow!\n");
exit(1);
}
if (input_buffer[0] == 'n' || input_buffer[0] == 'N') {
return 0;
} else if (input_buffer[0] == 'y' || input_buffer[0] == 'Y') {
return 1;
} else {
printf ("Please enter a N/n or Y/y character!\n");
exit(1);
}
}
// char *start_or_end should contain "start" or "end"
void get_dtstart_dtend(char input_buffer[], int all_day_event, char *start_or_end) {
if (all_day_event) {
printf("Enter the %s date in YYYY-mm-dd format!\n", start_or_end);
if (fgets(input_buffer, 128, stdin) == NULL) {
perror ("fgets");
exit(1);
}
if (strlen(input_buffer) != 11) {
printf ("Wrong format!\n");
exit(1);
}
} else {
printf("Enter the %s date in YYYY-mm-dd HH:MM:SS format!\n", start_or_end);
if (fgets(input_buffer, 128, stdin) == NULL) {
perror ("fgets");
exit(1);
}
if (strlen(input_buffer) != 20) {
printf ("Wrong format!\n");
exit(1);
}
}
}
void form_dtstart_string(char dtstart_buffer[], char time_zone[]) {
char dtstart_copy[strlen(dtstart_buffer)];
strcpy(dtstart_copy, dtstart_buffer);
// not all day event
if (strlen(dtstart_buffer) == 15) {
strcpy(dtstart_buffer, "DTSTART;TZID=/freeassociation.sourceforge.net");
strcat(dtstart_buffer, time_zone);
strcat(dtstart_buffer, ":\r\n ");
strcat(dtstart_buffer, dtstart_copy);
} else {
// all day event
strcpy(dtstart_buffer, "DTSTART;VALUE=DATE:");
strcat(dtstart_buffer, dtstart_copy);
}
strcat(dtstart_buffer, "\r\n");
}
void form_dtend_string(char dtend_buffer[], char time_zone[]) {
char dtend_copy[strlen(dtend_buffer)];
strcpy(dtend_copy, dtend_buffer);
// not all day event
if (strlen(dtend_buffer) == 15) {
strcpy(dtend_buffer, "DTEND;TZID=/freeassociation.sourceforge.net");
strcat(dtend_buffer, time_zone);
strcat(dtend_buffer, ":\r\n ");
strcat(dtend_buffer, dtend_copy);
} else {
// all day event
strcpy(dtend_buffer, "DTEND;VALUE=DATE:");
strcat(dtend_buffer, dtend_copy);
}
strcat(dtend_buffer, "\r\n");
}

8
src/insert_event.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
void insert_event(char *file_name);
void seek_cal_end(int fd);
int binary_user_choice();
void get_dtstart_dtend(char input_buffer[], int all_day_event, char *start_or_end);
void form_dtstart_string(char dtstart_buffer[], char time_zone[]);
void form_dtend_string(char dtend_buffer[], char time_zone[]);

View File

@ -6,27 +6,29 @@
void print_list(struct event *head) { void print_list(struct event *head) {
while (head != NULL) { while (head != NULL) {
printf("%s\n", head->date); printf("%s\n", head->start_date);
printf("%s\n", head->end_date);
printf("%s\n", head->summary); printf("%s\n", head->summary);
head = head->next; head = head->next;
} }
} }
void sorted_insert(struct event** head, char date[], char summary[]) { void sorted_insert(struct event** head, char start_date[], char end_date[], char summary[]) {
struct event *new_node = malloc(sizeof(struct event)); struct event *new_node = malloc(sizeof(struct event));
strcpy((*new_node).date, date); strcpy((*new_node).start_date, start_date);
strcpy((*new_node).end_date, end_date);
strcpy((*new_node).summary, summary); strcpy((*new_node).summary, summary);
if (*head == NULL || strcmp((*head)->date, new_node->date) >= 0) { if (*head == NULL || strcmp((*head)->start_date, new_node->start_date) >= 0) {
new_node->next = *head; new_node->next = *head;
*head = new_node; *head = new_node;
} }
else { else {
// Locate the node before the point of insertion // Locate the node before the point of insertion
struct event* current = *head; struct event* current = *head;
while (current->next!=NULL && strcmp(current->next->date, new_node->date) < 0) { while (current->next!=NULL && strcmp(current->next->start_date, new_node->start_date) < 0)
current = current->next; current = current->next;
}
new_node->next = current->next; new_node->next = current->next;
current->next = new_node; current->next = new_node;
} }
@ -44,12 +46,26 @@ void free_list(struct event *head)
} }
} }
void print_upcoming(struct event *head, char current_date[]) { // print_upcoming() also prints ongoing events
// because you usually also want to see those
void print_upcoming(struct event *head, char current_date[], int show_all_events) {
int i = 0;
while (head != NULL) { while (head != NULL) {
if (strcmp(head->date, current_date) >= 0) { if (strcmp(head->end_date, current_date) >= 0) {
pretty_print_date_time(head->date); pretty_print_date_time(head->start_date);
printf("\n%s\n", head->summary); print_end_date(head->end_date, head->start_date);
printf("\nSUMMARY: %s\n", head->summary);
if (!show_all_events) {
i++;
if (i > 4)
break;
}
if (head->next != NULL)
printf("\n");
} }
head = head->next;
head = head->next;
} }
} }

View File

@ -2,11 +2,12 @@
struct event { struct event {
char summary[256]; char summary[256];
char date[256]; char start_date[256];
char end_date[256];
struct event *next; struct event *next;
}; };
void print_list(struct event *head); void print_list(struct event *head);
void sorted_insert(struct event **head, char date[], char summary[]); void sorted_insert(struct event **head, char start_date[], char end_date[], char summary[]);
void free_list(struct event *head); void free_list(struct event *head);
void print_upcoming(struct event *head, char current_date[]); void print_upcoming(struct event *head, char current_date[], int show_all_events);

View File

@ -1,68 +1,27 @@
#include "list_handling.h"
#include "parse_ics.h"
#include "cli_arg_parsing.h" #include "cli_arg_parsing.h"
#include "date_time_handling.h" #include "date_time_handling.h"
#include "list_handling.h"
#include "cut_string.h"
#include "move_lines.h"
#include "read_until_nl.h"
#include "read_until_string.h"
#include "seek_string_a.h"
#include "remove_whitespace.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
int main(int argc, char **argv) { int main(int argc, char **argv) {
char *ics_path = ""; char *ics_path = "";
get_cli_args(argc, argv, &ics_path); int show_all_events = 0;
char my_line[4096] = ""; get_cli_args(argc, argv, &ics_path, &show_all_events);
int myfd = open(ics_path, O_RDONLY);
if (myfd == -1) {
perror ("Error opening file");
return 1;
}
// initialize linked list
struct event *head = NULL;
static char current_date[] = "xxxxxxxxTxxxxxx"; static char current_date[] = "xxxxxxxxTxxxxxx";
get_date(current_date); get_date(current_date);
printf ("Current date and time: "); printf ("Current date and time: ");
pretty_print_date_time(current_date); pretty_print_date_time(current_date);
printf ("\n"); printf ("\n\n");
char date[256] = ""; // initialize linked list
char summary[256] = ""; struct event *head = NULL;
while(read_until_nl(myfd, my_line)) { parse_ics_file(ics_path, &head);
if (strncmp(my_line, "BEGIN:VEVENT", 12) == 0) {
memset(my_line, '\0', sizeof(my_line));
// go to DTSTART, but dont write to a variable
seek_string_a(myfd, "DTSTART");
read_until_string(myfd, my_line, "DTEND");
remove_whitespace(my_line);
cut_string(my_line, ':', 1);
strcpy(date, my_line);
memset(my_line, '\0', sizeof(my_line)); print_upcoming(head, current_date, show_all_events);
seek_string_a(myfd, "SUMMARY:");
read_until_string(myfd, my_line, "TRANSP:");
remove_nl_and_cr(my_line);
strcpy(summary, my_line);
memset(my_line, '\0', sizeof(my_line));
sorted_insert(&head, date, summary);
memset(date, '\0', sizeof(date));
memset(summary, '\0', sizeof(summary));
}
memset(my_line, '\0', sizeof(my_line));
}
print_upcoming(head, current_date);
free_list(head); free_list(head);

5
src/meson.build Normal file
View File

@ -0,0 +1,5 @@
project('ics_cli', 'c')
executable('icscli', 'main.c', 'cli_arg_parsing.c', 'date_time_handling.c', 'insert_event.c', \
'list_handling.c', 'parse_ics.c', 'read_until_nl.c', 'read_until_string.c', 'seek_string_a.c', \
'string_handling.c', link_args : '-luuid', install: true, install_dir: '/usr/local/bin')

View File

@ -1,59 +0,0 @@
// functions that can move the file position conveniently
// remember that the last line of a file is usually empty and goes from '\n' to EOF
#include "move_lines.h"
#include <unistd.h>
// function to move back/up
void go_back_x_lines(int fd, int lines) {
for (int i = 0; i < lines; i++) {
seek_previous_line(fd);
}
}
void seek_previous_line(int fd) {
seek_line_start(fd);
lseek(fd, -1, SEEK_CUR);
seek_line_start(fd);
}
// set file position to the beginning of the line
void seek_line_start(int fd) {
char cur_char = '\0';
while (cur_char != '\n') {
int ret_lseek = lseek(fd, -1, SEEK_CUR);
if (ret_lseek == -1)
break;
read(fd, &cur_char, 1);
if (cur_char != '\n')
lseek(fd, -1, SEEK_CUR);
}
}
// function to move forward/down
void go_forward_x_lines(int fd, int lines) {
for (int i = 0; i < lines; i++) {
seek_next_line(fd);
}
}
void seek_next_line(int fd) {
seek_line_end(fd);
lseek(fd, 1, SEEK_CUR);
}
// set file position to the end of the line
void seek_line_end(int fd) {
char cur_char = '\0';
while (cur_char != '\n') {
// return code 0 of read indicates EOF
if (read(fd, &cur_char, 1) == 0)
break;
}
lseek(fd, -1, SEEK_CUR);
}

View File

@ -1,20 +0,0 @@
// functions that can move the file position conveniently
// remember that the last line of a file is usually empty and goes from '\n' to EOF
#pragma once
// function to move back/up
void go_back_x_lines(int fd, int lines);
void seek_previous_line(int fd);
// set file position to the beginning of the line
void seek_line_start(int fd);
// function to move forward/down
void go_forward_x_lines(int fd, int lines);
void seek_next_line(int fd);
// set file position to the end of the line
void seek_line_end(int fd);

102
src/parse_ics.c Normal file
View File

@ -0,0 +1,102 @@
#include "list_handling.h"
#include "parse_ics.h"
#include "read_until_nl.h"
#include "read_until_string.h"
#include "string_handling.h"
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void parse_event(char event_string[], struct event **head) {
char *start_date = strstr(event_string, "\nDTSTART");
char *end_date = strstr(event_string, "\nDTEND");
char *summary = strstr(event_string, "\nSUMMARY");
summary = strchr(summary, ':') + 1;
char *start_date_str = malloc(256);
char *end_date_str = malloc(256);
char *summary_str = malloc(512);
memset(start_date_str, '\0', 256);
memset(end_date_str, '\0', 256);
memset(summary_str, '\0', 512);
assert(start_date != NULL);
assert(end_date != NULL);
assert(summary != NULL);
strncpy(start_date_str, start_date,
strchr(start_date + 1, '\n') - start_date);
strncpy(end_date_str, end_date, strchr(end_date + 1, '\n') - end_date);
strncpy(summary_str, summary, strchr(summary + 1, '\n') - summary);
// parse dates
remove_whitespace(start_date_str);
cut_string(start_date_str, ':', 1);
remove_whitespace(end_date_str);
cut_string(end_date_str, ':', 1);
sorted_insert(head, start_date_str, end_date_str, summary_str);
free(start_date_str);
free(end_date_str);
free(summary_str);
}
// the unfolded string has a space
// as a separator between what where previously separate lines
void unfolding_string(char *folded_string, char *unfolded_string) {
int j = 0;
for (int i = 0; i < strlen(folded_string); i++) {
if (folded_string[i] == '\r' && folded_string[i + 1] == '\n'
&& isblank(folded_string[i + 2])) {
i += 3;
while (isblank(folded_string[i])) {
i++;
}
unfolded_string[j] = ' ';
j++;
unfolded_string[j] = folded_string[i];
j++;
} else {
unfolded_string[j] = folded_string[i];
j++;
}
}
}
/* this function takes the head of an empty initialized event list
* and the path to the ics file
* it will "fill" the list
*/
void parse_ics_file(char *file_path, struct event **head) {
char my_event[8192] = "";
char unfolded_event[8192] = "";
int myfd = open(file_path, O_RDONLY);
if (myfd == -1) {
perror ("Error opening file");
exit(1);
}
while(read_until_nl(myfd, my_event)) {
if (strncmp(my_event, "BEGIN:VEVENT", 12) == 0) {
// include the BEGIN:EVENT to not loose the new line of first field
lseek(myfd, -1, SEEK_CUR);
memset(my_event, '\0', sizeof(my_event));
read_until_string(myfd, my_event, "END:VEVENT");
unfolding_string(my_event, unfolded_event);
parse_event(unfolded_event, head);
}
memset(my_event, '\0', sizeof(my_event));
}
close(myfd);
}

5
src/parse_ics.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
void parse_event(char event_string[], struct event **head);
void unfolding_string(char* folded_string, char* unfolded_string);
void parse_ics_file(char *file_path, struct event **head);

View File

@ -1,45 +0,0 @@
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
#include <string.h>
void remove_nl_and_cr(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
// remove new lines, carriage returns and spaces
void remove_whitespace(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r' \
|| raw_string[i] == ' ') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}

View File

@ -1,8 +0,0 @@
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
#pragma once
void remove_nl_and_cr(char raw_string[]);
void remove_whitespace(char raw_string[]);

103
src/string_handling.c Normal file
View File

@ -0,0 +1,103 @@
// cut a string into two parts by the first occurence of delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
// cut a string into two parts by delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
#include <string.h>
#include <stdlib.h>
void cut_string(char my_string[], char delimiter, int side) {
char part1[256] = "";
char part2[256] = "";
int split = 0;
int j = 0;
for (int i = 0; i < strlen(my_string); i++) {
if (my_string[i] == delimiter) {
if (split == 0) {
split = 1;
continue;
}
}
if (split == 0) {
part1[i] = my_string[i];
} else {
part2[j] = my_string[i];
j++;
}
}
memset(my_string, '\0', strlen(my_string));
if (side == 0) {
strcpy(my_string, part1);
} else {
strcpy(my_string, part2);
}
}
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
void remove_nl_and_cr(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
// remove new lines, carriage returns and spaces
void remove_whitespace(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r' \
|| raw_string[i] == ' ') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
void remove_until_delim(char raw_string[], char delimiter, int occurence) {
int chunk = 0;
int j = 0;
char *tmp_string = malloc(strlen(raw_string));
memset(tmp_string, '\0', strlen(raw_string));
for (int i = 0; i < strlen(raw_string); i++) {
if (raw_string[i] == delimiter) {
chunk++;
}
if (chunk >= occurence) {
tmp_string[j] = raw_string[i];
j++;
}
}
strcpy(raw_string, tmp_string);
free(tmp_string);
}

8
src/string_handling.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
// cut string into two parts and choose one part
// side is 0 or 1
void cut_string(char my_string[], char delimiter, int side);
void remove_nl_and_cr(char raw_string[]);
void remove_whitespace(char raw_string[]);
void remove_until_delim(char raw_string[], char delimiter, int occurence);

View File

@ -301,9 +301,9 @@ BEGIN:VEVENT
UID:ce6cc0e3ca1bb6a22783ace61034b12c57325b4e UID:ce6cc0e3ca1bb6a22783ace61034b12c57325b4e
DTSTAMP:20230719T152822Z DTSTAMP:20230719T152822Z
DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin: DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230831T180000 20241005T140000
DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin: DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230831T220000 20241005T220000
SEQUENCE:3 SEQUENCE:3
SUMMARY:dentist SUMMARY:dentist
TRANSP:OPAQUE TRANSP:OPAQUE
@ -315,9 +315,9 @@ BEGIN:VEVENT
UID:be6dd0e3ca1bb6a44783ade61056b12c57325b5f UID:be6dd0e3ca1bb6a44783ade61056b12c57325b5f
DTSTAMP:20230819T152822Z DTSTAMP:20230819T152822Z
DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin: DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20240131T080000 20250131T080000
DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin: DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20240131T100000 20250131T100000
SEQUENCE:3 SEQUENCE:3
SUMMARY:do laundry SUMMARY:do laundry
TRANSP:OPAQUE TRANSP:OPAQUE
@ -325,4 +325,16 @@ CLASS:PUBLIC
CREATED:20230819T153023Z CREATED:20230819T153023Z
LAST-MODIFIED:20230819T153034Z LAST-MODIFIED:20230819T153034Z
END:VEVENT END:VEVENT
BEGIN:VEVENT
UID:ba584c4a-5369-4d9e-9b6f-157a3abda095
DTSTAMP:20230917T103047Z
DTSTART;VALUE=DATE:20250301
DTEND;VALUE=DATE:20250308
SEQUENCE:2
SUMMARY:vacation
TRANSP:OPAQUE
CLASS:PUBLIC
CREATED:20230917T103047Z
LAST-MODIFIED:20230917T103047Z
END:VEVENT
END:VCALENDAR END:VCALENDAR

297
tests/failed_cal.ics Normal file
View File

@ -0,0 +1,297 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
X-EVOLUTION-DATA-REVISION:2024-11-06T05:14:31.539689Z(0)
BEGIN:VTIMEZONE
TZID:UTC
BEGIN:STANDARD
DTSTART;VALUE=DATE:16010101
TZOFFSETFROM:+0000
TZOFFSETTO:+0000
END:STANDARD
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:Europe/Berlin
TZURL:http://tzurl.org/zoneinfo/Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+005328
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:18930401T000000
RDATE:18930401T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19160430T230000
RDATE:19160430T230000
RDATE:19170416T020000
RDATE:19180415T020000
RDATE:19400401T020000
RDATE:19430329T020000
RDATE:19440403T020000
RDATE:19450402T020000
RDATE:19460414T020000
RDATE:19470406T030000
RDATE:19480418T020000
RDATE:19490410T020000
RDATE:19800406T020000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19161001T010000
RDATE:19161001T010000
RDATE:19170917T030000
RDATE:19180916T030000
RDATE:19421102T030000
RDATE:19431004T030000
RDATE:19441002T030000
RDATE:19451118T030000
RDATE:19461007T030000
RDATE:19471005T030000
RDATE:19481003T030000
RDATE:19491002T030000
RDATE:19800928T030000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
TZNAME:CEMT
DTSTART:19450524T020000
RDATE:19450524T020000
RDATE:19470511T030000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19450924T030000
RDATE:19450924T030000
RDATE:19470629T030000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19460101T000000
RDATE:19460101T000000
RDATE:19800101T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:W. Europe Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:/freeassociation.sourceforge.net/Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+005328
TZOFFSETTO:+0100
DTSTART:18930401T000000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19160430T230000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19161001T010000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19170416T020000
RRULE:FREQ=YEARLY;UNTIL=19180415T010000Z;BYDAY=3MO;BYMONTH=4
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19170917T030000
RRULE:FREQ=YEARLY;UNTIL=19180916T010000Z;BYDAY=3MO;BYMONTH=9
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19400401T020000
RDATE:19430329T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19421102T030000
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19431004T030000
RRULE:FREQ=YEARLY;UNTIL=19441002T010000Z;BYDAY=1MO;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19440403T020000
RRULE:FREQ=YEARLY;UNTIL=19450402T010000Z;BYDAY=1MO;BYMONTH=4
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEMT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
DTSTART:19450524T020000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19450924T030000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19451118T030000
RDATE:19461007T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19460414T020000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19470406T030000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEMT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
DTSTART:19470511T030000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19470629T030000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19471005T030000
RRULE:FREQ=YEARLY;UNTIL=19491002T010000Z;BYDAY=1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19480418T020000
RDATE:19490410T020000
RDATE:19800406T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19800928T030000
RRULE:FREQ=YEARLY;UNTIL=19950924T010000Z;BYDAY=-1SU;BYMONTH=9
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;UNTIL=20370329T010000Z;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;UNTIL=20361026T010000Z;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:20371025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:20380328T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20251106T051352Z
DTSTART:20251215T080000Z
DTEND:20251215T083000Z
SUMMARY:Interview with John Doe
DESCRIPTION:foo
LOCATION:teams meeting
UID:35a4a000-dc20-47c6-b2b0-b4cbbfbb114e
CREATED:20241106T051431Z
LAST-MODIFIED:20241106T051431Z
END:VEVENT
END:VCALENDAR

View File

@ -0,0 +1,27 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//www.example.com//iCal 4.0.3//EN
METHOD:PUBLISH
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20240102T105004
UID:ical-19709628-example-com
DTSTART;VALUE=DATE:20250622T000000
DTEND;VALUE=DATE:20250711T000000
TRANSP:TRANSPARENT
SUMMARY:vacation
(approved)
(again)
DESCRIPTION:vacation\n\n
status: approved\n
workdays: 13,0\n\n
entered by: John Doe\n
entry date: Tue, 2024-01-02, 10:09 AM\n
approved on: -\n\n\n\n
Powered by example.com\n
DTSTAMP:20240102T105004
CATEGORIES:example.com
X-MICROSOFT-CDO-BUSYSTATUS:OOF
SEQUENCE:8
END:VEVENT
END:VCALENDAR

View File

@ -0,0 +1,27 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//www.example.com//iCal 4.0.3//EN
METHOD:PUBLISH
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20240102T105004
UID:ical-19709628-example-com
DTSTART;VALUE=DATE:20250622T000000
DTEND;VALUE=DATE:20250711T000000
TRANSP:TRANSPARENT
SUMMARY:vacation
(approved)
(again)
DESCRIPTION:vacation\n\n
status: approved\n
workdays: 13,0\n\n
entered by: John Doe\n
entry date: Tue, 2024-01-02, 10:09 AM\n
approved on: -\n\n\n\n
Powered by example.com\n
DTSTAMP:20240102T105004
CATEGORIES:example.com
X-MICROSOFT-CDO-BUSYSTATUS:OOF
SEQUENCE:8
END:VEVENT
END:VCALENDAR

19
tests/received_ical.ics Normal file
View File

@ -0,0 +1,19 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//www.example.com//iCal 4.0.3//EN
METHOD:PUBLISH
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20240102T105004
UID:ical-19709628-example-com
DTSTART;VALUE=DATE:20240622T000000
DTEND;VALUE=DATE:20240711T000000
TRANSP:TRANSPARENT
SUMMARY:vacation (approved)
DESCRIPTION:vacation\n\nstatus: approved\nworkdays: 13,0\n\nentered by: John Doe\nentry date: Tue, 2024-01-02, 10:09 AM\napproved on: -\n\n\n\nPowered by example.com\n
DTSTAMP:20240102T105004
CATEGORIES:example.com
X-MICROSOFT-CDO-BUSYSTATUS:OOF
SEQUENCE:8
END:VEVENT
END:VCALENDAR

15
tests/run_tests.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
mapfile -t files < <(find ../tests/. -name "*.ics")
i=1
for file in ${files[@]}; do
echo "TEST ${i}: ${file}"
echo "===================================================="
../src/icscli -f "${file}"
echo "===================================================="
echo
((i++))
done
exit 0

View File

@ -0,0 +1,12 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20240413T070000Z
DTEND:20240413T083000Z
DTSTAMP:20240229T171200Z
SUMMARY:XYZ Exam (XYZ)
DESCRIPTION:XYZ Exam (XYZ)
UID:61792
CREATED:20240229T171308Z
LAST-MODIFIED:20240229T171308Z
END:VEVENT
END:VCALENDAR

16
unit-tests/meson.build Normal file
View File

@ -0,0 +1,16 @@
project('ics_cli', 'c')
t1e = executable('test_print_upcoming', 'test_print_upcoming.c', '../src/parse_ics.c', '../src/string_handling.c', \
'../src/list_handling.c', '../src/date_time_handling.c', '../src/read_until_string.c', '../src/read_until_nl.c')
test('test print_upcoming', t1e)
t2e = executable('test_pretty_print_date_time', 'test_pretty_print_date_time.c', '../src/string_handling.c', \
'../src/date_time_handling.c', '../src/read_until_string.c', '../src/read_until_nl.c')
test('test pretty_print_date_time', t2e)
t3e = executable('test_parse_ics_file', 'test_parse_ics_file.c', '../src/parse_ics.c', '../src/string_handling.c', '../src/list_handling.c', \
'../src/date_time_handling.c', '../src/read_until_string.c', '../src/read_until_nl.c')
test('test parse_ics_file', t3e)

View File

@ -0,0 +1,14 @@
#include "../src/list_handling.h"
#include "../src/parse_ics.h"
#include <stdio.h>
int main() {
// initialize empty list
struct event *head = NULL;
parse_ics_file("../../tests/calendar.ics", &head);
parse_ics_file("../../tests/failed_cal.ics", &head);
return 0;
}

View File

@ -0,0 +1,50 @@
#include "../src/date_time_handling.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main() {
// 1
char* pdt = pretty_date_time("20251215T080000Z");
assert(!strcmp(pdt, "2025-12-15 08:00:00"));
free(pdt);
// 2
char* pdt2 = pretty_date_time("20251215T080000");
assert(!strcmp(pdt2, "2025-12-15 08:00:00"));
free(pdt2);
// 3
char* pdt3 = pretty_date_time("");
assert(!strcmp(pdt3, ""));
free(pdt3);
// 4
char* pdt4 = pretty_date_time("20251215");
assert(!strcmp(pdt4, "2025-12-15"));
free(pdt4);
// 5
char current_date[] = "20240710T103000";
printf("current_date: %s\n", current_date);
printf("strlen(current_date): %ld\n\n", strlen(current_date));
pretty_print_date_time(current_date);
printf("\n\ncurrent_date: %s\n", current_date);
printf("strlen(current_date): %ld\n", strlen(current_date));
pretty_print_date_time("20251215T080000Z");
printf("\n");
return 0;
}

View File

@ -0,0 +1,24 @@
#include "../src/list_handling.h"
#include "../src/parse_ics.h"
#include <stdio.h>
int main() {
// initialize empty list
struct event *head = NULL;
// 1
printf("\nTesting tests/failed_cal.ics:\n\n");
char *current_date = "20240710T113000";
printf("DEBUG - current_date: %s\n\n", current_date);
parse_ics_file("../../tests/failed_cal.ics", &head);
print_upcoming(head, current_date, 0);
// 2
printf("\nTesting tests/calendar.ics:\n\n");
parse_ics_file("../../tests/calendar.ics", &head);
print_upcoming(head, current_date, 0);
}