Compare commits
78 Commits
36ede8c52d
...
master
Author | SHA1 | Date | |
---|---|---|---|
4313a7ef84 | |||
3bf6fea43d | |||
8e4c37fe88 | |||
2e60c19b0a | |||
ed4d5cc798 | |||
a7d2a49931 | |||
7bef5ea855 | |||
28d6710c0a | |||
f69d56d91a | |||
8bdb19d0c0 | |||
58e0c8feb3 | |||
750d450b75 | |||
0eda58f63f | |||
ad96144904 | |||
7e36e9143d | |||
773bd82216 | |||
c20f1ca1f0 | |||
578bf0b0f5 | |||
33d9fed0cf | |||
9bba31490b | |||
2c6f4bb357 | |||
2442af328b | |||
37b6e1ce2c | |||
3ead0c07b2 | |||
c4fc332419 | |||
ad344b03ac | |||
7809fb64ab | |||
d225e6aa41 | |||
5a96c95f69 | |||
f2015b062c | |||
a8006f2e28 | |||
7fc1a17d3b | |||
f248a1d279 | |||
97208505d8 | |||
75cacc3e04 | |||
0c1e64a16e | |||
31b8c83d2f | |||
c786ccf659 | |||
76c7f414ef | |||
3252bf67ae | |||
ff20bb92f7 | |||
7a5b3bc97a | |||
b2a2d09a92 | |||
f874fb99a6 | |||
ecfae506c9 | |||
ce356065a7 | |||
05d59f6697 | |||
5b31aec8d6 | |||
bf6fdd71eb | |||
6af368a69c | |||
ea72a21418 | |||
55c6a39ee8 | |||
add437c484 | |||
0e85ea09cd | |||
af46177bcd | |||
4ff2e705e2 | |||
f9a617a882 | |||
bf6d09e99a | |||
78a07b128f | |||
315d127717 | |||
925d9b35ce | |||
167457b791 | |||
e8ed8b0319 | |||
0773d4f561 | |||
0955921918 | |||
c484acb88e | |||
0206625a76 | |||
0cd92b929d | |||
061ed30333 | |||
c713522736 | |||
47a7d134ed | |||
b3d7d6b45b | |||
3b348066bd | |||
bdface378e | |||
1d0e412d86 | |||
455d60f47f | |||
d8cb527f61 | |||
3a53493e74 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,5 @@
|
|||||||
a.out
|
icscli
|
||||||
.gitconfig
|
.gitconfig
|
||||||
|
*.o
|
||||||
|
*.out
|
||||||
|
tags
|
||||||
|
52
README.md
52
README.md
@ -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
79
docs/ics_format.txt
Normal 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
29
git-hooks/pre-commit
Executable 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
|
32
makefile
32
makefile
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
187
src/insert_event.c
Normal 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
8
src/insert_event.h
Normal 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[]);
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
61
src/main.c
61
src/main.c
@ -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
5
src/meson.build
Normal 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')
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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
102
src/parse_ics.c
Normal 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
5
src/parse_ics.h
Normal 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);
|
@ -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);
|
|
||||||
}
|
|
@ -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
103
src/string_handling.c
Normal 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
8
src/string_handling.h
Normal 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);
|
@ -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
297
tests/failed_cal.ics
Normal 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
|
27
tests/folded_with_spaces.ics
Normal file
27
tests/folded_with_spaces.ics
Normal 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
|
27
tests/folded_with_tabs.ics
Normal file
27
tests/folded_with_tabs.ics
Normal 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
19
tests/received_ical.ics
Normal 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
15
tests/run_tests.sh
Executable 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
|
12
tests/start_date_is_first_field.ics
Normal file
12
tests/start_date_is_first_field.ics
Normal 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
16
unit-tests/meson.build
Normal 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)
|
14
unit-tests/test_parse_ics_file.c
Normal file
14
unit-tests/test_parse_ics_file.c
Normal 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;
|
||||||
|
}
|
50
unit-tests/test_pretty_print_date_time.c
Normal file
50
unit-tests/test_pretty_print_date_time.c
Normal 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;
|
||||||
|
}
|
24
unit-tests/test_print_upcoming.c
Normal file
24
unit-tests/test_print_upcoming.c
Normal 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);
|
||||||
|
}
|
Reference in New Issue
Block a user