Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timezones #5

Merged
merged 13 commits into from
Oct 24, 2021
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use text\ical\{
Event,
Organizer,
Attendee,
Date,
IDate,
Text,
Method,
Role,
Expand Down Expand Up @@ -93,8 +93,8 @@ $calendar= Calendar::with()
->value('MAILTO:attendee3@example.com')
->create()
])
->dtstart(new Date(null, '20160524T183000Z'))
->dtend(new Date(null, '20160524T190000Z'))
->dtstart(new IDate(null, '20160524T183000Z'))
->dtend(new IDate(null, '20160524T190000Z'))
->location(new Text('de-DE', 'BS 50 EG 0102'))
->summary(new Text('de-DE', 'Treffen'))
->create()
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/autoload.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php namespace xp;

\lang\ClassLoader::registerPath(__DIR__);
\lang\ClassLoader::registerPath(__DIR__);
22 changes: 22 additions & 0 deletions src/main/php/module.xp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php namespace text\ical;

module ical {

/**
* Ensures `text.ical.Date` and `text.ical.TimeZone` can be instantiated
* when imported; the classes now have an *I* prefix in order to work in
* conjunction with the classes from XP Framework Core - as we'd like to
* retain backwards compatibility we help ourselves with `class_alias()`.
*
* @return void
*/
public function initialize() {
spl_autoload_register(function($class) {
switch ($class) {
case Date::class: return class_alias(IDate::class, Date::class);
case TimeZone::class: return class_alias(ITimeZone::class, TimeZone::class);
default: return false;
}
});
}
}
37 changes: 28 additions & 9 deletions src/main/php/text/ical/Calendar.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php namespace text\ical;

use lang\IllegalStateException;
use util\Date;
use util\Objects;

class Calendar implements IObject {
Expand All @@ -14,15 +16,15 @@ class Calendar implements IObject {
* @param string $prodid
* @param string $version
* @param text.ical.Event[] $events
* @param text.ical.TimeZone $timezone
* @param text.ical.ITimeZone[] $timezones
* @param [:string] $properties
*/
public function __construct($method, $prodid, $version, $events, $timezone, $properties= []) {
public function __construct($method, $prodid, $version, $events, $timezones, $properties= []) {
$this->method= $method;
$this->prodid= $prodid;
$this->version= $version;
$this->events= $events;
$this->timezone= $timezone;
$this->timezones= $timezones;
$this->properties= $properties;
}

Expand All @@ -35,35 +37,52 @@ public function prodid() { return $this->prodid; }
/** @return string */
public function version() { return $this->version; }

/** @return text.ical.TimeZone */
public function timezone() { return $this->timezone; }
/** @return text.ical.ITimeZone[] */
public function timezones() { return $this->timezones; }

/** @return text.ical.Events */
public function events() { return new Events(...(array)$this->events); }

/** @return object */
public static function with() {
return new class() {
private $method, $prodid, $version, $events, $timezone, $properties= [];
private $method, $prodid, $version, $events, $timezones, $properties= [];

public function method($value) { $this->method= $value; return $this; }

public function prodid($value) { $this->prodid= $value; return $this; }

public function version($value) { $this->version= $value; return $this; }

public function timezone($value) { $this->timezone= $value; return $this; }
public function timezones($value) { $this->timezones= $value; return $this; }

public function events($value) { $this->events= $value; return $this; }

public function properties($value) { $this->properties= $value; return $this; }

public function create() {
return new Calendar($this->method, $this->prodid, $this->version, $this->events, $this->timezone, $this->properties);
return new Calendar($this->method, $this->prodid, $this->version, $this->events, $this->timezones, $this->properties);
}
};
}

/**
* Converts a calendar date value to a date instance
*
* @param text.ical.IDate $date
* @return util.Date
* @throws lang.IllegalStateException if the date's timezone is not defined
*/
public function date(IDate $date) {
if (null === ($tzid= $date->tzid())) return new Date($date->value());

foreach ($this->timezones as $timezone) {
if ($tzid === $timezone->tzid()) return $timezone->convert($date->value());
}

throw new IllegalStateException('No timezone definition in calendar for "'.$tzid.'"');
}

/**
* Write this object
*
Expand All @@ -77,7 +96,7 @@ public function write($out, $name) {
'prodid' => $this->prodid,
'version' => $this->version,
'event' => $this->events,
'timezone' => $this->timezone
'timezone' => $this->timezones
]));
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/php/text/ical/Creation.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ class Creation {
const CHECK = true;

private static $definitions= [null, null, [
'calendar' => [Calendar::class, ['events' => 'event'], [
'calendar' => [Calendar::class, ['events' => 'event', 'timezones' => 'timezone'], [
'event' => [Event::class, ['attendees' => 'attendee'], [
'organizer' => [Organizer::class],
'attendee' => [Attendee::class],
'summary' => [Text::class],
'description' => [Text::class],
'comment' => [Text::class],
'location' => [Text::class],
'dtstart' => [Date::class],
'dtstamp' => [Date::class],
'dtend' => [Date::class],
'dtstart' => [IDate::class],
'dtstamp' => [IDate::class],
'dtend' => [IDate::class],
'alarm' => [Alarm::class, null, [
'trigger' => [Trigger::class]
]]
]],
'timezone' => [TimeZone::class, null, [
'timezone' => [ITimeZone::class, null, [
'standard' => [TimeZoneInfo::class],
'daylight' => [TimeZoneInfo::class],
]],
Expand Down
12 changes: 6 additions & 6 deletions src/main/php/text/ical/Event.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class Event implements IObject {
* @param text.ical.Text $description
* @param text.ical.Text $summary
* @param text.ical.Text $comment
* @param text.ical.Date $dtstart
* @param text.ical.Date $dtend
* @param text.ical.Date $dtstamp
* @param text.ical.IDate $dtstart
* @param text.ical.IDate $dtend
* @param text.ical.IDate $dtstamp
* @param string $uid
* @param string $class
* @param string $priority
Expand Down Expand Up @@ -60,13 +60,13 @@ public function summary() { return $this->summary; }
/** @return text.ical.Text */
public function comment() { return $this->comment; }

/** @return text.ical.Date */
/** @return text.ical.IDate */
public function dtstart() { return $this->dtstart; }

/** @return text.ical.Date */
/** @return text.ical.IDate */
public function dtend() { return $this->dtend; }

/** @return text.ical.Date */
/** @return text.ical.IDate */
public function dtstamp() { return $this->dtstamp; }

/** @return string */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use util\Objects;

class Date implements IObject {
class IDate implements IObject {
private $tzid, $value;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php namespace text\ical;

use util\Objects;
use util\{Date, Objects};

class TimeZone implements IObject {
class ITimeZone implements IObject {
private $tzid, $standard, $daylight;

/**
Expand Down Expand Up @@ -39,11 +39,31 @@ public function standard($value) { $this->standard= $value; return $this; }
public function daylight($value) { $this->daylight= $value; return $this; }

public function create() {
return new TimeZone($this->tzid, $this->standard, $this->daylight);
return new ITimeZone($this->tzid, $this->standard, $this->daylight);
}
};
}

/**
* Converts a date
*
* @param string $input `YYYYMMDD"T"HHMMSS`
* @return util.Date
*/
public function convert($input) {
$date= sscanf($input, '%4d%2d%2dT%2d%2d%d');

$rel= gmmktime($date[3], $date[4], $date[5], $date[1], $date[2], $date[0]);
$daylight= $this->daylight->start($date[0]);
$standard= $this->standard->start($date[0]);

if ($rel >= $standard + $this->standard->adjust() || $rel < $daylight + $this->daylight->adjust()) {
return new Date(gmdate('Y-m-d H:i:s'.$this->standard->tzoffsetto(), $rel));
} else {
return new Date(gmdate('Y-m-d H:i:s'.$this->daylight->tzoffsetto(), $rel));
}
}

/**
* Write this object
*
Expand Down
60 changes: 60 additions & 0 deletions src/main/php/text/ical/TimeZoneInfo.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace text\ical;

use lang\IllegalStateException;
use util\Objects;

class TimeZoneInfo implements IObject {
Expand Down Expand Up @@ -51,6 +52,65 @@ public function create() {
};
}

/** @return int */
public function offset() {
sscanf($this->tzoffsetto, "%c%2d:%d", $sign, $h, $m);
return ('-' === $sign ? -1 : 1) * ($h * 3600 + $m * 60);
}

/** @return int */
public function adjust() {
sscanf($this->tzoffsetfrom, "%c%2d:%d", $sign, $h, $m);
$from= ('-' === $sign ? -1 : 1) * ($h * 3600 + $m * 60);
sscanf($this->tzoffsetto, "%c%2d:%d", $sign, $h, $m);
$to= ('-' === $sign ? -1 : 1) * ($h * 3600 + $m * 60);
return $to - $from;
}

/**
* Returns start of this time for a given year in GMT
*
* @param int $year
* @return int
*/
public function start($year) {
static $days= ['MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6, 'SU' => 0];

$start= sscanf($this->dtstart, '%4d%2d%2dT%2d%2d%d');
if (null === $this->rrule) {
return gmmktime($start[3], $start[4], $start[5], $start[1], $start[2], $year);
} else {

// RRULE: https://tools.ietf.org/html/rfc5545#section-3.3.10
$r= [];
foreach (explode(';', $this->rrule) as $attributes) {
sscanf($attributes, "%[^=]=%[^\r]", $key, $value);
$r[$key]= $value;
}

if ('YEARLY' !== $r['FREQ']) {
throw new IllegalStateException('Unexpected frequency '.$r['FREQ']);
}

// -1SU = "Last Sunday in month"
// 1SU = "First Sunday in month"
// 2SU = "Second Sunday in month"
if ('-' === $r['BYDAY'][0]) {
$month= (int)$r['BYMONTH'] + 1;
$by= $days[substr($r['BYDAY'], 2)];
$last= idate('w', gmmktime(0, 0, 0, $month, -1, $year));
$day= $by - $last - 1;
} else {
$month= (int)$r['BYMONTH'];
$by= $days[substr($r['BYDAY'], 1)];
$first= idate('w', gmmktime(0, 0, 0, $month, 0, $year));
$day= $by + $first + 1 + 7 * ($r['BYDAY'][0] - 1);
}

return gmmktime($start[3], $start[4], $start[5], $month, $day, $year);
}
}

/**
* Write this object
*
Expand Down
13 changes: 7 additions & 6 deletions src/test/php/text/ical/unittest/Fixtures.class.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?php namespace text\ical\unittest;

use text\ical\{Alarm, Attendee, Calendar, Date, Event, Organizer, Text, TimeZone, TimeZoneInfo, Trigger};
use lang\Enum;
use text\ical\{Alarm, Attendee, Calendar, IDate, Event, Organizer, Text, ITimeZone, TimeZoneInfo, Trigger};

class Fixtures extends \lang\Enum {
class Fixtures extends Enum {
public static $calendar, $event, $timezone, $alarm, $quoting, $properties;

static function __static() {
Expand Down Expand Up @@ -74,8 +75,8 @@ static function __static() {
->value('MAILTO:attendee3@example.com')
->create()
])
->dtstart(new Date('W. Europe Standard Time', '20160524T183000'))
->dtend(new Date('W. Europe Standard Time', '20160524T190000'))
->dtstart(new IDate('W. Europe Standard Time', '20160524T183000'))
->dtend(new IDate('W. Europe Standard Time', '20160524T190000'))
->location(new Text('de-DE', 'BS 50 EG 0102'))
->comment(new Text('de-DE', "\n"))
->summary(new Text('de-DE', 'Treffen'))
Expand Down Expand Up @@ -106,7 +107,7 @@ static function __static() {
END:VTIMEZONE
END:VCALENDAR
',
Calendar::with()->timezone(new TimeZone(
Calendar::with()->timezones([new ITimeZone(
'W. Europe Standard Time',
TimeZoneInfo::with()
->dtstart('16010101T030000')
Expand All @@ -121,7 +122,7 @@ static function __static() {
->tzoffsetto('+0200')
->rrule('FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3')
->create()
))
)])
->create()
);

Expand Down
Loading