Skip to content

Commit

Permalink
Merge pull request #5 from xp-forge/feature/timezones
Browse files Browse the repository at this point in the history
Timezones
  • Loading branch information
thekid authored Oct 24, 2021
2 parents 6703ccd + 4010e90 commit 54628e1
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 31 deletions.
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

0 comments on commit 54628e1

Please sign in to comment.