calcard
is a Rust crate for parsing, generating, and converting calendaring and contact data across multiple formats. It supports iCalendar and vCard, making it easy to work with .ics
and .vcf
files. It also fully handles JSCalendar and JSContact, the emerging standards commonly used in JMAP-based systems. In addition to parsing and serializing these formats, calcard
provides seamless conversion between iCalendar and JSCalendar, as well as between vCard and JSContact, enabling smooth interoperability between traditional and modern calendaring and contact protocols.
In general, this library abides by the Postel's law or Robustness Principle which states that an implementation must be conservative in its sending behavior and liberal in its receiving behavior. This means that calcard
will make a best effort to parse non-conformant iCal/vCard/JSCalendar/JSContact objects as long as these do not deviate too much from the standard.
Comprehensive features for working with calendaring and contact data, including:
- iCalendar support: Parsing and generating iCalendar data.
- vCard support: Parsing and generating vCard data.
- JSCalendar support: Parsing and generating JSCalendar data.
- JSContact support: Parsing and generating JSContact data.
- Seamless conversion between iCalendar and JSCalendar.
- Seamless conversion between vCard and JSContact.
- Recurrence rules expansion: Accurately computes and enumerates repeating events based on iCalendar and JSCalendar RRULEs.
- IANA timezone detection: Automatically resolves and handles custom and proprietary timezones.
You can parse vCard and iCalendar files using the stream-based parser:
let input = "BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nEND:VCARD\n";
let mut parser = Parser::new(&input);
loop {
match parser.entry() {
Entry::VCard(vcard) => println!("Parsed VCard: {:?}", vcard),
Entry::ICalendar(ical) => println!("Parsed ICalendar: {:?}", ical),
Entry::InvalidLine(line) => eprintln!("Invalid line found: {}", line),
Entry::UnexpectedComponentEnd { expected, found } => {
eprintln!("Unexpected end: expected {:?}, found {:?}", expected, found);
},
Entry::UnterminatedComponent(component) => {
eprintln!("Unterminated component: {:?}", component);
},
Entry::Eof => {
break;
}
}
}
You can parse a single iCalendar using the ICalendar::parse
method:
let input = "BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR\n";
let ical = ICalendar::parse(&input);
println!("Parsed ICalendar: {:?}", ical);
You can parse a single vCard using the VCard::parse
method:
let input = "BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nEND:VCARD\n";
let vcard = VCard::parse(&input);
println!("Parsed VCard: {:?}", vcard);
You can parse a JSCalendar JSON string using the JSCalendar::parse
method:
let input = r#"{
"@type": "Group",
"uid": "bf0ac22b-4989-4caf-9ebd-54301b4ee51a",
"updated": "2020-01-15T18:00:00Z",
"title": "A simple group",
"entries": [{
"@type": "Event",
"uid": "a8df6573-0474-496d-8496-033ad45d7fea",
"updated": "2020-01-02T18:23:04Z",
"title": "Some event",
"start": "2020-01-15T13:00:00",
"timeZone": "America/New_York",
"duration": "PT1H"
},
{
"@type": "Task",
"uid": "2a358cee-6489-4f14-a57f-c104db4dc2f2",
"updated": "2020-01-09T14:32:01Z",
"title": "Do something"
}]
}"#;
let jscalendar = JSCalendar::parse(input).unwrap();
println!("Parsed JSCalendar: {}", jscalendar.to_string_pretty());
You can parse a JSContact JSON string using the JSContact::parse
method:
let input = r#"{
"@type": "Card",
"version": "1.0",
"uid": "22B2C7DF-9120-4969-8460-05956FE6B065",
"kind": "individual",
"name": {
"components": [
{ "kind": "given", "value": "John" },
{ "kind": "surname", "value": "Doe" }
],
"isOrdered": true
}
}"#;
let jscontact = JSContact::parse(input).unwrap();
println!("Parsed JSContact: {}", jscontact.to_string_pretty());
To convert a JSCalendar to an iCalendar, use the JSCalendar::into_icalendar
method:
let ical = JSCalendar::parse(input).unwrap().into_icalendar().unwrap();
To convert an iCalendar to a JSCalendar, use the ICalendar::into_jscalendar
method:
let jscalendar = ICalendar::parse(&input).unwrap().into_jscalendar().unwrap();
To convert a JSContact to a vCard, use the JSContact::into_vcard
method:
let vcard = JSContact::parse(input).unwrap().into_vcard().unwrap();
To convert a vCard to a JSContact, use the VCard::into_jscontact
method:
let jscontact = VCard::parse(&input).unwrap().into_jscontact().unwrap();
To generate a vCard or iCalendar, simply call .to_string()
on the parsed object:
let vcard_string = vcard.to_string();
println!("Generated vCard:\n{}", vcard_string);
let ical_string = ical.to_string();
println!("Generated ICalendar:\n{}", ical_string);
Note: Documentation for creating VCard and ICalendar objects is coming soon.
To generate a JSCalendar or JSContact, simply call .to_string()
on the parsed object or .to_string_pretty()
for a more human-readable format:
let jscalendar_string = jscalendar.to_string();
println!("Generated JSCalendar:\n{}", jscalendar_string);
let jscontact_string = jscontact.to_string();
println!("Generated JSContact:\n{}", jscontact_string);
Note: Documentation for creating JSCalendar and JSContact objects is coming soon.
To run the testsuite:
$ cargo test --all-features
or, to run the testsuite with MIRI:
$ cargo +nightly miri test --all-features
To fuzz the library with cargo-fuzz
:
$ cargo +nightly fuzz run fuzz_all
$ cargo +nightly fuzz run fuzz_random
$ cargo +nightly fuzz run fuzz_structured
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Part of the development of this library was funded through the NGI Zero Core, a fund established by NLnet with financial support from the European Commission's programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 101092990.
If you find this library useful you can help by becoming a sponsor. Thank you!
Copyright (C) 2020, Stalwart Labs LLC