year = $year; $this->month = $month; $this->day = $day; } } /** */ class struct_time { public int $hour; public int $minute; public int $second; public bool $utc; public function __construct( int $hour, int $minute, int $second, bool $utc ) { $this->hour = $hour; $this->minute = $minute; $this->second = $second; $this->utc = $utc; } } /** */ class struct_datetime { public struct_date $date; public ?struct_time $time; public function __construct( struct_date $date, ?struct_time $time ) { $this->date = $date; $this->time = $time; } } /** */ class struct_dt { public /*type_tzid*/string $tzid; public struct_datetime $value; public function __construct( string $tzid, struct_datetime $value ) { $this->tzid = $tzid; $this->value = $value; } } /** */ class struct_duration { public bool $negative; public ?int $weeks; public ?int $days; public ?int $hours; public ?int $minutes; public ?int $seconds; } /** */ class struct_vtimezone_entry { public struct_datetime $dtstart; public struct_rrule $rrule; public ?/*type_offset*/string $tzoffsetfrom; public ?/*type_offset*/string $tzoffsetto; } /** */ class struct_vtimezone { public ?string/*type_tzid*/ $tzid; public ?struct_vtimezone_entry $standard; public ?struct_vtimezone_entry $daylight; } /** */ class struct_geo { public float $latitude; public float $longitude; } /** */ class struct_organizer { public ?string $value; public ?string $cn; public ?string $dir; public ?string $sent_by; } /** * @see https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1 */ class struct_vevent { // required public string $uid; public struct_datetime $dtstamp; // required if "method" is not specified in the parent public ?struct_dt $dtstart; // optional public ?enum_class $class; public ?struct_datetime $created; public ?string $description; public ?struct_geo $geo; public ?struct_datetime $last_modified; public ?string $location; /** * @see https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.3 */ public ?struct_organizer $organizer; public ?int $priority; public ?int $sequence; public ?enum_event_status $status; public ?string $summary; public ?enum_transp $transp; public ?string $url; public $recurid; public ?type_rrule $rrule; // either or public ?struct_dt $dtend; public ?type_duration $duration; // optional public $attach; public ?string $attendee; public /*list*/?array $categories; public $comment; public $contact; public $exdate; public $rstatus; public $related; public $resources; public $rdate; // extra public /*map*/?array $x_props; public /*map*/?array $iana_props; } /** * @see https://www.rfc-editor.org/rfc/rfc5545#section-3.4 */ class struct_vcalendar { // required public string $version; public string $prodid; public /*list*/array $vevents; // optional public ?string $calscale; public ?string $method; public ?struct_vtimezone $vtimezone; // extra public /*map*/?array $x_props; public /*map*/?array $iana_props; }; /** */ function date_decode( string $date_encoded ) : struct_date { return (new struct_date( \intval(\substr($date_encoded, 0, 4)), \intval(\substr($date_encoded, 4, 2)), \intval(\substr($date_encoded, 6, 2)) )); } /** */ function time_decode( string $time_encoded ) : struct_time { return (new struct_time( \intval(\substr($time_encoded, 0, 2)), \intval(\substr($time_encoded, 2, 2)), \intval(\substr($time_encoded, 4, 2)), ((\strlen($time_encoded) >= 7) && ($time_encoded[6] === 'Z')) )); } /** */ function datetime_decode( string $datetime_encoded ) : struct_datetime { $parts = \explode('T', $datetime_encoded, 2); return (new struct_datetime( date_decode($parts[0]), ((\count($parts) >= 2) ? time_decode($parts[1]) : null) )); } /** */ enum enum_decode_state_label { case expect_vcalendar_begin/* = "expect_vcalendar_begin"*/; case expect_vcalendar_property/* = "expect_vcalendar_property"*/; case expect_vevent_property/* = "expect_vevent_property"*/; case done/* = "done"*/; } /** */ function class_encode( enum_class $class ) : string { switch ($class) { case enum_class::private_: {return 'PRIVATE'; break;} case enum_class::public_: {return 'PUBLIC'; break;} case enum_class::confidential: {return 'CONFIDENTIAL'; break;} } } /** */ function class_decode( string $class_encoded ) : enum_class { return [ 'PRIVATE' => enum_class::private_, 'PUBLIC' => enum_class::public_, 'CONFIDENTIAL' => enum_class::confidential, ][$class_encoded]; } /** */ function event_status_encode( enum_event_status $event_status ) : string { switch ($event_status) { case enum_event_status::tentative: {return 'TENTATIVE'; break;} case enum_event_status::confirmed: {return 'CONFIRMED'; break;} case enum_event_status::cancelled: {return 'CANCELLED'; break;} } } /** */ function event_status_decode( string $event_status_encoded ) : enum_event_status { return [ 'TENTATIVE' => enum_event_status::tentative, 'CONFIRMED' => enum_event_status::confirmed, 'CANCELLED' => enum_event_status::cancelled, ][$event_status_encoded]; } /** */ function transp_encode( enum_transp $transp ) : string { switch (transp) { case enum_transp::opaque: {return 'OPAQUE'; break;} case enum_transp::transparent: {return 'TRANSPARENT'; break;} } } /** */ function transp_decode( string $transp_encoded ) : enum_transp { return [ 'OPAQUE' => enum_transp::opaque, 'TRANSPARENT' => enum_transp::transparent, ][$transp_encoded]; } /** */ /* function datetime_to_unixtimestamp( struct_datetime $datetime ) : int { if (($datetime->time !== null) && (! $datetime->time->utc)) { throw (new \Exception('can not convert not utc time values')); } else { return lib_plankton.pit.from_datetime( { "timezone_shift": 0, "date": { "year": datetime.date.year, "month": datetime.date.month, "day": datetime.date.day, }, "time": { "hour": ((datetime.time === null) ? 0 : datetime.time.hour), "minute": ((datetime.time === null) ? 0 : datetime.time.minute), "second": ((datetime.time === null) ? 0 : datetime.time.second), } } ); } } */ /** */ function vcalendar_decode( string $ics ) : struct_vcalendar { $path = \sprintf('/tmp/foo.ics'); \file_put_contents($path, $ics); $ical = new \ICal\ICal( $path, [ 'defaultSpan' => 2, // Default value 'defaultTimeZone' => 'UTC', 'defaultWeekStart' => 'MO', // Default value 'disableCharacterReplacement' => false, // Default value 'filterDaysAfter' => null, // Default value 'filterDaysBefore' => null, // Default value 'httpUserAgent' => null, // Default value 'skipRecurrence' => false, // Default value ] ); // $ical->initFile($path); /** * @todo transform correctly */ $result = new struct_vcalendar(); $result->events = \array_map( function ($event_raw) { $vevent = new struct_vevent(); $vevent->uid = $event_raw->uid; $vevent->summary = $event_raw->summary; $vevent->dtstart = (new struct_dt( '', datetime_decode($event_raw->dtstart) )); $vevent->dtend = ( ($event_raw->dtend === null) ? null : (new struct_dt( '', datetime_decode($event_raw->dtend) )) ); $vevent->location = $event_raw->location; $vevent->description = $event_raw->description; $vevent->categories = \array_reduce( \array_map( fn($x) => \explode(',', $x), \array_values( \array_filter( ($event_raw->additionalProperties['categories_array'] ?? []), fn($x) => \is_string($x) ) ) ), fn($x, $y) => \array_merge($x, $y), [] ); return $vevent; }, $ical->events() ); return $result; } /** * @see https://www.rfc-editor.org/rfc/rfc5545 * @see https://icalendar.org/iCalendar-RFC-5545/ */ function date_encode( struct_date $date ) : string { return \davina\helpers\string_\coin( '{{year}}{{month}}{{day}}', [ 'year' => \str_pad(\sprintf('%u', $date->year), 4, '0', \STR_PAD_LEFT), 'month' => \str_pad(\sprintf('%u', $date->month), 2, '0', \STR_PAD_LEFT), 'day' => \str_pad(\sprintf('%u', $date->day), 2, '0', \STR_PAD_LEFT), ] ); } /** */ function time_encode( struct_time $time ) : string { return \davina\helpers\string_\coin( '{{hour}}{{minute}}{{second}}{{utc}}', [ 'hour' => \str_pad(\sprintf('%u', $time->hour), 2, '0', \STR_PAD_LEFT), 'minute' => \str_pad(\sprintf('%u', $time->minute), 2, '0', \STR_PAD_LEFT), 'second' => \str_pad(\sprintf('%u', $time->second), 2, '0', \STR_PAD_LEFT), 'utc' => ($time->utc ? 'Z' : ''), ] ); } /** */ function datetime_encode( struct_datetime $datetime ) : string { return \davina\helpers\string_\coin( '{{date}}T{{time}}', [ 'date' => date_encode($datetime->date), 'time' => time_encode($datetime->time), ] ); } /** * @todo complete */ function vcalendar_encode( struct_vcalendar $vcalendar ) : string { $content_lines = []; \array_push($content_lines, 'BEGIN:VCALENDAR'); \array_push($content_lines, \davina\helpers\string_\coin('VERSION:{{version}}', ['version' => $vcalendar->version])); \array_push($content_lines, \davina\helpers\string_\coin('PRODID:{{prodid}}', ['prodid' => $vcalendar->prodid])); \array_push($content_lines, \davina\helpers\string_\coin('METHOD:{{method}}', ['method' => $vcalendar->method])); foreach ($vcalendar->events as $vevent) { \array_push($content_lines, 'BEGIN:VEVENT'); { // uid \array_push( $content_lines, \davina\helpers\string_\coin( 'UID:{{uid}}', [ 'uid' => $vevent->uid, ] ) ); // dtstart \array_push( $content_lines, \davina\helpers\string_\coin( 'DTSTART:{{dtstart}}', [ 'dtstart' => datetime_encode($vevent->dtstart->value), ] ) ); // dtend if ($vevent->dtend !== null) { \array_push( $content_lines, \davina\helpers\string_\coin( 'DTEND:{{dtend}}', [ 'dtend' => datetime_encode($vevent->dtend->value), ] ) ); } // dtstamp \array_push( $content_lines, \davina\helpers\string_\coin( 'DTSTAMP:{{dtstamp}}', [ 'dtstamp' => datetime_encode($vevent->dtstamp), ] ) ); // class if ($vevent->class !== null) { \array_push( $content_lines, \davina\helpers\string_\coin( 'CLASS:{{class}}', [ 'class' => class_encode($vevent->class), ] ) ); } // summary \array_push( $content_lines, \davina\helpers\string_\coin( 'SUMMARY:{{summary}}', [ 'summary' => $vevent->summary, ] ) ); // description if ($vevent->description !== null) { \array_push( $content_lines, \davina\helpers\string_\coin( 'DESCRIPTION:{{description}}', [ 'description' => $vevent->description, ] ) ); } // location if ($vevent->location !== null) { \array_push( $content_lines, \davina\helpers\string_\coin( 'LOCATION:{{location}}', [ 'location' => $vevent->location, ] ) ); } /* // geo if (vevent.geo !== undefined) { content_lines.push( lib_plankton.string.coin( "GEO:{{geo_latitude}};{{geo_longitude}}", { "geo_latitude": vevent.geo.latitude.toFixed(4), "geo_longitude": vevent.geo.longitude.toFixed(4), } ) ); } */ /* // url if (vevent.url !== undefined) { content_lines.push( lib_plankton.string.coin( "URL:{{url}}", { "url": vevent.url, } ) ); } */ // categories if ( ($vevent->categories !== null) && (\count($vevent->categories) > 0) ) { \array_push( $content_lines, \davina\helpers\string_\coin( 'CATEGORIES:{{categories}}', [ 'categories' => \implode(',', $vevent->categories), ] ) ); } } \array_push($content_lines, 'END:VEVENT'); } \array_push($content_lines, 'END:VCALENDAR'); $lines = []; foreach ($content_lines as $content_line) { $slices = \davina\helpers\string_\slice($content_line, 75 - 1); \array_push($lines, $slices[0]); foreach (\array_slice($slices, 1) as $slice) { \array_push($lines, ' ' . $slice); } } return \implode("\r\n", $lines); } /** */ function datetime_to_unix_timestamp( struct_datetime $datetime ) : int { return \davina\helpers\pit\pit_to_unix_timestamp( \davina\helpers\pit\pit_from_datetime( new \davina\helpers\pit\struct_datetime( 0, new \davina\helpers\pit\struct_date( $datetime->date->year, $datetime->date->month, $datetime->date->day ), ( ($datetime->time === null) ? null : new \davina\helpers\pit\struct_time( $datetime->time->hour, $datetime->time->minute, $datetime->time->second ) ) ) ) ); } /** */ function datetime_from_unix_timestamp( int $unix_timestamp ) : struct_datetime { $pit = \davina\helpers\pit\pit_from_unix_timestamp($unix_timestamp); $datetime = \davina\helpers\pit\pit_to_datetime($pit); return (new struct_datetime( new struct_date( $datetime->date->year, $datetime->date->month, $datetime->date->day ), ( ($datetime->time === null) ? null : new struct_time( $datetime->time->hour, $datetime->time->minute, $datetime->time->second, true ) ) )); } ?>