Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
391 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/plugins/calendar/lib/Horde_Date.php b/plugins/calendar/lib/Horde_Date.php
new file mode 100644
index 00000000..18709d9f
--- /dev/null
+++ b/plugins/calendar/lib/Horde_Date.php
@@ -0,0 +1,775 @@
+<?php
+
+/**
+ * This is a concatenated copy of the following files:
+ * Horde/Date.php, PEAR/Date/Calc.php, Horde/Date/Recurrence.php
+ */
+
+define('HORDE_DATE_SUNDAY', 0);
+define('HORDE_DATE_MONDAY', 1);
+define('HORDE_DATE_TUESDAY', 2);
+define('HORDE_DATE_WEDNESDAY', 3);
+define('HORDE_DATE_THURSDAY', 4);
+define('HORDE_DATE_FRIDAY', 5);
+define('HORDE_DATE_SATURDAY', 6);
+
+define('HORDE_DATE_MASK_SUNDAY', 1);
+define('HORDE_DATE_MASK_MONDAY', 2);
+define('HORDE_DATE_MASK_TUESDAY', 4);
+define('HORDE_DATE_MASK_WEDNESDAY', 8);
+define('HORDE_DATE_MASK_THURSDAY', 16);
+define('HORDE_DATE_MASK_FRIDAY', 32);
+define('HORDE_DATE_MASK_SATURDAY', 64);
+define('HORDE_DATE_MASK_WEEKDAYS', 62);
+define('HORDE_DATE_MASK_WEEKEND', 65);
+define('HORDE_DATE_MASK_ALLDAYS', 127);
+
+define('HORDE_DATE_MASK_SECOND', 1);
+define('HORDE_DATE_MASK_MINUTE', 2);
+define('HORDE_DATE_MASK_HOUR', 4);
+define('HORDE_DATE_MASK_DAY', 8);
+define('HORDE_DATE_MASK_MONTH', 16);
+define('HORDE_DATE_MASK_YEAR', 32);
+define('HORDE_DATE_MASK_ALLPARTS', 63);
+
+/**
+ * Horde Date wrapper/logic class, including some calculation
+ * functions.
+ *
+ * $Horde: framework/Date/Date.php,v 1.8.10.18 2008/09/17 08:46:04 jan Exp $
+ *
+ * @package Horde_Date
+ */
+class Horde_Date {
+
+ /**
+ * Year
+ *
+ * @var integer
+ */
+ var $year;
+
+ /**
+ * Month
+ *
+ * @var integer
+ */
+ var $month;
+
+ /**
+ * Day
+ *
+ * @var integer
+ */
+ var $mday;
+
+ /**
+ * Hour
+ *
+ * @var integer
+ */
+ var $hour = 0;
+
+ /**
+ * Minute
+ *
+ * @var integer
+ */
+ var $min = 0;
+
+ /**
+ * Second
+ *
+ * @var integer
+ */
+ var $sec = 0;
+
+ /**
+ * Internally supported strftime() specifiers.
+ *
+ * @var string
+ */
+ var $_supportedSpecs = '%CdDeHImMnRStTyY';
+
+ /**
+ * Build a new date object. If $date contains date parts, use them to
+ * initialize the object.
+ *
+ * Recognized formats:
+ * - arrays with keys 'year', 'month', 'mday', 'day' (since Horde 3.2),
+ * 'hour', 'min', 'minute' (since Horde 3.2), 'sec'
+ * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
+ * - yyyy-mm-dd hh:mm:ss (since Horde 3.1)
+ * - yyyymmddhhmmss (since Horde 3.1)
+ * - yyyymmddThhmmssZ (since Horde 3.1.4)
+ * - unix timestamps
+ */
+ function Horde_Date($date = null)
+ {
+ if (function_exists('nl_langinfo')) {
+ $this->_supportedSpecs .= 'bBpxX';
+ }
+
+ if (is_array($date) || is_object($date)) {
+ foreach ($date as $key => $val) {
+ if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
+ $this->$key = (int)$val;
+ }
+ }
+
+ // If $date['day'] is present and numeric we may have been passed
+ // a Horde_Form_datetime array.
+ if (is_array($date) && isset($date['day']) &&
+ is_numeric($date['day'])) {
+ $this->mday = (int)$date['day'];
+ }
+ // 'minute' key also from Horde_Form_datetime
+ if (is_array($date) && isset($date['minute'])) {
+ $this->min = $date['minute'];
+ }
+ } elseif (!is_null($date)) {
+ // Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'.
+ if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) {
+ $this->year = (int)$parts[1];
+ $this->month = (int)$parts[2];
+ $this->mday = (int)$parts[3];
+ $this->hour = (int)$parts[4];
+ $this->min = (int)$parts[5];
+ $this->sec = (int)$parts[6];
+ } else {
+ // Try as a timestamp.
+ $parts = @getdate($date);
+ if ($parts) {
+ $this->year = $parts['year'];
+ $this->month = $parts['mon'];
+ $this->mday = $parts['mday'];
+ $this->hour = $parts['hours'];
+ $this->min = $parts['minutes'];
+ $this->sec = $parts['seconds'];
+ }
+ }
+ }
+ }
+
+ /**
+ * @static
+ */
+ function isLeapYear($year)
+ {
+ if (strlen($year) != 4 || preg_match('/\D/', $year)) {
+ return false;
+ }
+
+ return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
+ }
+
+ /**
+ * Returns the day of the year (1-366) that corresponds to the
+ * first day of the given week.
+ *
+ * TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php
+ *
+ * @param integer $week The week of the year to find the first day of.
+ * @param integer $year The year to calculate for.
+ *
+ * @return integer The day of the year of the first day of the given week.
+ */
+ function firstDayOfWeek($week, $year)
+ {
+ $jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1));
+ $start = $jan1->dayOfWeek();
+ if ($start > HORDE_DATE_THURSDAY) {
+ $start -= 7;
+ }
+ return (($week * 7) - (7 + $start)) + 1;
+ }
+
+ /**
+ * @static
+ */
+ function daysInMonth($month, $year)
+ {
+ if ($month == 2) {
+ if (Horde_Date::isLeapYear($year)) {
+ return 29;
+ } else {
+ return 28;
+ }
+ } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
+ return 30;
+ } else {
+ return 31;
+ }
+ }
+
+ /**
+ * Return the day of the week (0 = Sunday, 6 = Saturday) of this
+ * object's date.
+ *
+ * @return integer The day of the week.
+ */
+ function dayOfWeek()
+ {
+ if ($this->month > 2) {
+ $month = $this->month - 2;
+ $year = $this->year;
+ } else {
+ $month = $this->month + 10;
+ $year = $this->year - 1;
+ }
+
+ $day = (floor((13 * $month - 1) / 5) +
+ $this->mday + ($year % 100) +
+ floor(($year % 100) / 4) +
+ floor(($year / 100) / 4) - 2 *
+ floor($year / 100) + 77);
+
+ return (int)($day - 7 * floor($day / 7));
+ }
+
+ /**
+ * Returns the day number of the year (1 to 365/366).
+ *
+ * @return integer The day of the year.
+ */
+ function dayOfYear()
+ {
+ $monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
+ $dayOfYear = $this->mday + $monthTotals[$this->month - 1];
+ if (Horde_Date::isLeapYear($this->year) && $this->month > 2) {
+ ++$dayOfYear;
+ }
+
+ return $dayOfYear;
+ }
+
+ /**
+ * Returns the week of the month.
+ *
+ * @since Horde 3.2
+ *
+ * @return integer The week number.
+ */
+ function weekOfMonth()
+ {
+ return ceil($this->mday / 7);
+ }
+
+ /**
+ * Returns the week of the year, first Monday is first day of first week.
+ *
+ * @return integer The week number.
+ */
+ function weekOfYear()
+ {
+ return $this->format('W');
+ }
+
+ /**
+ * Return the number of weeks in the given year (52 or 53).
+ *
+ * @static
+ *
+ * @param integer $year The year to count the number of weeks in.
+ *
+ * @return integer $numWeeks The number of weeks in $year.
+ */
+ function weeksInYear($year)
+ {
+ // Find the last Thursday of the year.
+ $day = 31;
+ $date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0));
+ while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) {
+ --$date->mday;
+ }
+ return $date->weekOfYear();
+ }
+
+ /**
+ * Set the date of this object to the $nth weekday of $weekday.
+ *
+ * @param integer $weekday The day of the week (0 = Sunday, etc).
+ * @param integer $nth The $nth $weekday to set to (defaults to 1).
+ */
+ function setNthWeekday($weekday, $nth = 1)
+ {
+ if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) {
+ return false;
+ }
+
+ $this->mday = 1;
+ $first = $this->dayOfWeek();
+ if ($weekday < $first) {
+ $this->mday = 8 + $weekday - $first;
+ } else {
+ $this->mday = $weekday - $first + 1;
+ }
+ $this->mday += 7 * $nth - 7;
+
+ $this->correct();
+
+ return true;
+ }
+
+ function dump($prefix = '')
+ {
+ echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "<br />\n";
+ }
+
+ /**
+ * Is the date currently represented by this object a valid date?
+ *
+ * @return boolean Validity, counting leap years, etc.
+ */
+ function isValid()
+ {
+ if ($this->year < 0 || $this->year > 9999) {
+ return false;
+ }
+ return checkdate($this->month, $this->mday, $this->year);
+ }
+
+ /**
+ * Correct any over- or underflows in any of the date's members.
+ *
+ * @param integer $mask We may not want to correct some overflows.
+ */
+ function correct($mask = HORDE_DATE_MASK_ALLPARTS)
+ {
+ if ($mask & HORDE_DATE_MASK_SECOND) {
+ while ($this->sec < 0) {
+ --$this->min;
+ $this->sec += 60;
+ }
+ while ($this->sec > 59) {
+ ++$this->min;
+ $this->sec -= 60;
+ }
+ }
+
+ if ($mask & HORDE_DATE_MASK_MINUTE) {
+ while ($this->min < 0) {
+ --$this->hour;
+ $this->min += 60;
+ }
+ while ($this->min > 59) {
+ ++$this->hour;
+ $this->min -= 60;
+ }
+ }
+
+ if ($mask & HORDE_DATE_MASK_HOUR) {
+ while ($this->hour < 0) {
+ --$this->mday;
+ $this->hour += 24;
+ }
+ while ($this->hour > 23) {
+ ++$this->mday;
+ $this->hour -= 24;
+ }
+ }
+
+ if ($mask & HORDE_DATE_MASK_MONTH) {
+ while ($this->month > 12) {
+ ++$this->year;
+ $this->month -= 12;
+ }
+ while ($this->month < 1) {
+ --$this->year;
+ $this->month += 12;
+ }
+ }
+
+ if ($mask & HORDE_DATE_MASK_DAY) {
+ while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) {
+ $this->mday -= Horde_Date::daysInMonth($this->month, $this->year);
+ ++$this->month;
+ $this->correct(HORDE_DATE_MASK_MONTH);
+ }
+ while ($this->mday < 1) {
+ --$this->month;
+ $this->correct(HORDE_DATE_MASK_MONTH);
+ $this->mday += Horde_Date::daysInMonth($this->month, $this->year);
+ }
+ }
+ }
+
+ /**
+ * Compare this date to another date object to see which one is
+ * greater (later). Assumes that the dates are in the same
+ * timezone.
+ *
+ * @param mixed $date The date to compare to.
+ *
+ * @return integer == 0 if the dates are equal
+ * >= 1 if this date is greater (later)
+ * <= -1 if the other date is greater (later)
+ */
+ function compareDate($date)
+ {
+ if (!is_object($date) || !is_a($date, 'Horde_Date')) {
+ $date = new Horde_Date($date);
+ }
+
+ if ($this->year != $date->year) {
+ return $this->year - $date->year;
+ }
+ if ($this->month != $date->month) {
+ return $this->month - $date->month;
+ }
+
+ return $this->mday - $date->mday;
+ }
+
+ /**
+ * Compare this to another date object by time, to see which one
+ * is greater (later). Assumes that the dates are in the same
+ * timezone.
+ *
+ * @param mixed $date The date to compare to.
+ *
+ * @return integer == 0 if the dates are equal
+ * >= 1 if this date is greater (later)
+ * <= -1 if the other date is greater (later)
+ */
+ function compareTime($date)
+ {
+ if (!is_object($date) || !is_a($date, 'Horde_Date')) {
+ $date = new Horde_Date($date);
+ }
+
+ if ($this->hour != $date->hour) {
+ return $this->hour - $date->hour;
+ }
+ if ($this->min != $date->min) {
+ return $this->min - $date->min;
+ }
+
+ return $this->sec - $date->sec;
+ }
+
+ /**
+ * Compare this to another date object, including times, to see
+ * which one is greater (later). Assumes that the dates are in the
+ * same timezone.
+ *
+ * @param mixed $date The date to compare to.
+ *
+ * @return integer == 0 if the dates are equal
+ * >= 1 if this date is greater (later)
+ * <= -1 if the other date is greater (later)
+ */
+ function compareDateTime($date)
+ {
+ if (!is_object($date) || !is_a($date, 'Horde_Date')) {
+ $date = new Horde_Date($date);
+ }
+
+ if ($diff = $this->compareDate($date)) {
+ return $diff;
+ }
+
+ return $this->compareTime($date);
+ }
+
+ /**
+ * Get the time offset for local time zone.
+ *
+ * @param boolean $colon Place a colon between hours and minutes?
+ *
+ * @return string Timezone offset as a string in the format +HH:MM.
+ */
+ function tzOffset($colon = true)
+ {
+ $secs = $this->format('Z');
+
+ if ($secs < 0) {
+ $sign = '-';
+ $secs = -$secs;
+ } else {
+ $sign = '+';
+ }
+ $colon = $colon ? ':' : '';
+ $mins = intval(($secs + 30) / 60);
+ return sprintf('%s%02d%s%02d',
+ $sign, $mins / 60, $colon, $mins % 60);
+ }
+
+ /**
+ * Return the unix timestamp representation of this date.
+ *
+ * @return integer A unix timestamp.
+ */
+ function timestamp()
+ {
+ if (class_exists('DateTime')) {
+ return $this->format('U');
+ } else {
+ return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year);
+ }
+ }
+
+ /**
+ * Return the unix timestamp representation of this date, 12:00am.
+ *
+ * @return integer A unix timestamp.
+ */
+ function datestamp()
+ {
+ if (class_exists('DateTime')) {
+ $dt = new DateTime();
+ $dt->setDate($this->year, $this->month, $this->mday);
+ $dt->setTime(0, 0, 0);
+ return $dt->format('U');
+ } else {
+ return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year);
+ }
+ }
+
+ /**
+ * Format time using the specifiers available in date() or in the DateTime
+ * class' format() method.
+ *
+ * @since Horde 3.3
+ *
+ * @param string $format
+ *
+ * @return string Formatted time.
+ */
+ function format($format)
+ {
+ if (class_exists('DateTime')) {
+ $dt = new DateTime();
+ $dt->setDate($this->year, $this->month, $this->mday);
+ $dt->setTime($this->hour, $this->min, $this->sec);
+ return $dt->format($format);
+ } else {
+ return date($format, $this->timestamp());
+ }
+ }
+
+ /**
+ * Format time in ISO-8601 format. Works correctly since Horde 3.2.
+ *
+ * @return string Date and time in ISO-8601 format.
+ */
+ function iso8601DateTime()
+ {
+ return $this->rfc3339DateTime() . $this->tzOffset();
+ }
+
+ /**
+ * Format time in RFC 2822 format.
+ *
+ * @return string Date and time in RFC 2822 format.
+ */
+ function rfc2822DateTime()
+ {
+ return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false);
+ }
+
+ /**
+ * Format time in RFC 3339 format.
+ *
+ * @since Horde 3.1
+ *
+ * @return string Date and time in RFC 3339 format. The seconds part has
+ * been added with Horde 3.2.
+ */
+ function rfc3339DateTime()
+ {
+ return $this->format('Y-m-d\TH:i:s');
+ }
+
+ /**
+ * Format time to standard 'ctime' format.
+ *
+ * @return string Date and time.
+ */
+ function cTime()
+ {
+ return $this->format('D M j H:i:s Y');
+ }
+
+ /**
+ * Format date and time using strftime() format.
+ *
+ * @since Horde 3.1
+ *
+ * @return string strftime() formatted date and time.
+ */
+ function strftime($format)
+ {
+ if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) {
+ return strftime($format, $this->timestamp());
+ } else {
+ return $this->_strftime($format);
+ }
+ }
+
+ /**
+ * Format date and time using a limited set of the strftime() format.
+ *
+ * @return string strftime() formatted date and time.
+ */
+ function _strftime($format)
+ {
+ if (preg_match('/%[bBpxX]/', $format)) {
+ require_once 'Horde/NLS.php';
+ }
+
+ return preg_replace(
+ array('/%b/e',
+ '/%B/e',
+ '/%C/e',
+ '/%d/e',
+ '/%D/e',
+ '/%e/e',
+ '/%H/e',
+ '/%I/e',
+ '/%m/e',
+ '/%M/e',
+ '/%n/',
+ '/%p/e',
+ '/%R/e',
+ '/%S/e',
+ '/%t/',
+ '/%T/e',
+ '/%x/e',
+ '/%X/e',
+ '/%y/e',
+ '/%Y/',
+ '/%%/'),
+ array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))',
+ '$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))',
+ '(int)($this->year / 100)',
+ 'sprintf(\'%02d\', $this->mday)',
+ '$this->_strftime(\'%m/%d/%y\')',
+ 'sprintf(\'%2d\', $this->mday)',
+ 'sprintf(\'%02d\', $this->hour)',
+ 'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))',
+ 'sprintf(\'%02d\', $this->month)',
+ 'sprintf(\'%02d\', $this->min)',
+ "\n",
+ '$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))',
+ '$this->_strftime(\'%H:%M\')',
+ 'sprintf(\'%02d\', $this->sec)',
+ "\t",
+ '$this->_strftime(\'%H:%M:%S\')',
+ '$this->_strftime(NLS::getLangInfo(D_FMT))',
+ '$this->_strftime(NLS::getLangInfo(T_FMT))',
+ 'substr(sprintf(\'%04d\', $this->year), -2)',
+ (int)$this->year,
+ '%'),
+ $format);
+ }
+
+ /**
+ * mktime() implementation that supports dates outside of 1970-2038,
+ * from http://phplens.com/phpeverywhere/adodb_date_library.
+ *
+ * @TODO remove in Horde 4
+ *
+ * This does NOT work with pre-1970 daylight saving times.
+ *
+ * @static
+ */
+ function _mktime($hr, $min, $sec, $mon = false, $day = false,
+ $year = false, $is_dst = false, $is_gmt = false)
+ {
+ if ($mon === false) {
+ return $is_gmt
+ ? @gmmktime($hr, $min, $sec)
+ : @mktime($hr, $min, $sec);
+ }
+
+ if ($year > 1901 && $year < 2038 &&
+ ($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) {
+ return $is_gmt
+ ? @gmmktime($hr, $min, $sec, $mon, $day, $year)
+ : @mktime($hr, $min, $sec, $mon, $day, $year);
+ }
+
+ $gmt_different = $is_gmt
+ ? 0
+ : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0));
+
+ $mon = intval($mon);
+ $day = intval($day);
+ $year = intval($year);
+
+ if ($mon > 12) {
+ $y = floor($mon / 12);
+ $year += $y;
+ $mon -= $y * 12;
+ } elseif ($mon < 1) {
+ $y = ceil((1 - $mon) / 12);
+ $year -= $y;
+ $mon += $y * 12;
+ }
+
+ $_day_power = 86400;
+ $_hour_power = 3600;
+ $_min_power = 60;
+
+ $_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+ $_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+
+ $_total_date = 0;
+ if ($year >= 1970) {
+ for ($a = 1970; $a <= $year; $a++) {
+ $leaf = Horde_Date::isLeapYear($a);
+ if ($leaf == true) {
+ $loop_table = $_month_table_leaf;
+ $_add_date = 366;
+ } else {
+ $loop_table = $_month_table_normal;
+ $_add_date = 365;
+ }
+ if ($a < $year) {
+ $_total_date += $_add_date;
+ } else {
+ for ($b = 1; $b < $mon; $b++) {
+ $_total_date += $loop_table[$b];
+ }
+ }
+ }
+
+ return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
+ }
+
+ for ($a = 1969 ; $a >= $year; $a--) {
+ $leaf = Horde_Date::isLeapYear($a);
+ if ($leaf == true) {
+ $loop_table = $_month_table_leaf;
+ $_add_date = 366;
+ } else {
+ $loop_table = $_month_table_normal;
+ $_add_date = 365;
+ }
+ if ($a > $year) {
+ $_total_date += $_add_date;
+ } else {
+ for ($b = 12; $b > $mon; $b--) {
+ $_total_date += $loop_table[$b];
+ }
+ }
+ }
+
+ $_total_date += $loop_table[$mon] - $day;
+ $_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
+ $_day_time = $_day_power - $_day_time;
+ $ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
+ if ($ret < -12220185600) {
+ // If earlier than 5 Oct 1582 - gregorian correction.
+ return $ret + 10 * 86400;
+ } elseif ($ret < -12219321600) {
+ // If in limbo, reset to 15 Oct 1582.
+ return -12219321600;
+ } else {
+ return $ret;
+ }
+ }
+
+}
+
diff --git a/plugins/calendar/lib/Horde_Date_Recurrence.php b/plugins/calendar/lib/Horde_Date_Recurrence.php
index 68340ba3..fbf1d1eb 100644
--- a/plugins/calendar/lib/Horde_Date_Recurrence.php
+++ b/plugins/calendar/lib/Horde_Date_Recurrence.php
@@ -1,6746 +1,5972 @@
<?php
-/**
- * This is a concatenated copy of the following files:
- * Horde/Date.php, PEAR/Date/Calc.php, Horde/Date/Recurrence.php
- */
-
-define('HORDE_DATE_SUNDAY', 0);
-define('HORDE_DATE_MONDAY', 1);
-define('HORDE_DATE_TUESDAY', 2);
-define('HORDE_DATE_WEDNESDAY', 3);
-define('HORDE_DATE_THURSDAY', 4);
-define('HORDE_DATE_FRIDAY', 5);
-define('HORDE_DATE_SATURDAY', 6);
-
-define('HORDE_DATE_MASK_SUNDAY', 1);
-define('HORDE_DATE_MASK_MONDAY', 2);
-define('HORDE_DATE_MASK_TUESDAY', 4);
-define('HORDE_DATE_MASK_WEDNESDAY', 8);
-define('HORDE_DATE_MASK_THURSDAY', 16);
-define('HORDE_DATE_MASK_FRIDAY', 32);
-define('HORDE_DATE_MASK_SATURDAY', 64);
-define('HORDE_DATE_MASK_WEEKDAYS', 62);
-define('HORDE_DATE_MASK_WEEKEND', 65);
-define('HORDE_DATE_MASK_ALLDAYS', 127);
-
-define('HORDE_DATE_MASK_SECOND', 1);
-define('HORDE_DATE_MASK_MINUTE', 2);
-define('HORDE_DATE_MASK_HOUR', 4);
-define('HORDE_DATE_MASK_DAY', 8);
-define('HORDE_DATE_MASK_MONTH', 16);
-define('HORDE_DATE_MASK_YEAR', 32);
-define('HORDE_DATE_MASK_ALLPARTS', 63);
-
-/**
- * Horde Date wrapper/logic class, including some calculation
- * functions.
- *
- * $Horde: framework/Date/Date.php,v 1.8.10.18 2008/09/17 08:46:04 jan Exp $
- *
- * @package Horde_Date
- */
-class Horde_Date {
-
- /**
- * Year
- *
- * @var integer
- */
- var $year;
-
- /**
- * Month
- *
- * @var integer
- */
- var $month;
-
- /**
- * Day
- *
- * @var integer
- */
- var $mday;
-
- /**
- * Hour
- *
- * @var integer
- */
- var $hour = 0;
-
- /**
- * Minute
- *
- * @var integer
- */
- var $min = 0;
-
- /**
- * Second
- *
- * @var integer
- */
- var $sec = 0;
-
- /**
- * Internally supported strftime() specifiers.
- *
- * @var string
- */
- var $_supportedSpecs = '%CdDeHImMnRStTyY';
-
- /**
- * Build a new date object. If $date contains date parts, use them to
- * initialize the object.
- *
- * Recognized formats:
- * - arrays with keys 'year', 'month', 'mday', 'day' (since Horde 3.2),
- * 'hour', 'min', 'minute' (since Horde 3.2), 'sec'
- * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
- * - yyyy-mm-dd hh:mm:ss (since Horde 3.1)
- * - yyyymmddhhmmss (since Horde 3.1)
- * - yyyymmddThhmmssZ (since Horde 3.1.4)
- * - unix timestamps
- */
- function Horde_Date($date = null)
- {
- if (function_exists('nl_langinfo')) {
- $this->_supportedSpecs .= 'bBpxX';
- }
-
- if (is_array($date) || is_object($date)) {
- foreach ($date as $key => $val) {
- if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
- $this->$key = (int)$val;
- }
- }
-
- // If $date['day'] is present and numeric we may have been passed
- // a Horde_Form_datetime array.
- if (is_array($date) && isset($date['day']) &&
- is_numeric($date['day'])) {
- $this->mday = (int)$date['day'];
- }
- // 'minute' key also from Horde_Form_datetime
- if (is_array($date) && isset($date['minute'])) {
- $this->min = $date['minute'];
- }
- } elseif (!is_null($date)) {
- // Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'.
- if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) {
- $this->year = (int)$parts[1];
- $this->month = (int)$parts[2];
- $this->mday = (int)$parts[3];
- $this->hour = (int)$parts[4];
- $this->min = (int)$parts[5];
- $this->sec = (int)$parts[6];
- } else {
- // Try as a timestamp.
- $parts = @getdate($date);
- if ($parts) {
- $this->year = $parts['year'];
- $this->month = $parts['mon'];
- $this->mday = $parts['mday'];
- $this->hour = $parts['hours'];
- $this->min = $parts['minutes'];
- $this->sec = $parts['seconds'];
- }
- }
- }
- }
-
- /**
- * @static
- */
- function isLeapYear($year)
- {
- if (strlen($year) != 4 || preg_match('/\D/', $year)) {
- return false;
- }
-
- return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
- }
-
- /**
- * Returns the day of the year (1-366) that corresponds to the
- * first day of the given week.
- *
- * TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php
- *
- * @param integer $week The week of the year to find the first day of.
- * @param integer $year The year to calculate for.
- *
- * @return integer The day of the year of the first day of the given week.
- */
- function firstDayOfWeek($week, $year)
- {
- $jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1));
- $start = $jan1->dayOfWeek();
- if ($start > HORDE_DATE_THURSDAY) {
- $start -= 7;
- }
- return (($week * 7) - (7 + $start)) + 1;
- }
-
- /**
- * @static
- */
- function daysInMonth($month, $year)
- {
- if ($month == 2) {
- if (Horde_Date::isLeapYear($year)) {
- return 29;
- } else {
- return 28;
- }
- } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
- return 30;
- } else {
- return 31;
- }
- }
-
- /**
- * Return the day of the week (0 = Sunday, 6 = Saturday) of this
- * object's date.
- *
- * @return integer The day of the week.
- */
- function dayOfWeek()
- {
- if ($this->month > 2) {
- $month = $this->month - 2;
- $year = $this->year;
- } else {
- $month = $this->month + 10;
- $year = $this->year - 1;
- }
-
- $day = (floor((13 * $month - 1) / 5) +
- $this->mday + ($year % 100) +
- floor(($year % 100) / 4) +
- floor(($year / 100) / 4) - 2 *
- floor($year / 100) + 77);
-
- return (int)($day - 7 * floor($day / 7));
- }
-
- /**
- * Returns the day number of the year (1 to 365/366).
- *
- * @return integer The day of the year.
- */
- function dayOfYear()
- {
- $monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
- $dayOfYear = $this->mday + $monthTotals[$this->month - 1];
- if (Horde_Date::isLeapYear($this->year) && $this->month > 2) {
- ++$dayOfYear;
- }
-
- return $dayOfYear;
- }
-
- /**
- * Returns the week of the month.
- *
- * @since Horde 3.2
- *
- * @return integer The week number.
- */
- function weekOfMonth()
- {
- return ceil($this->mday / 7);
- }
-
- /**
- * Returns the week of the year, first Monday is first day of first week.
- *
- * @return integer The week number.
- */
- function weekOfYear()
- {
- return $this->format('W');
- }
-
- /**
- * Return the number of weeks in the given year (52 or 53).
- *
- * @static
- *
- * @param integer $year The year to count the number of weeks in.
- *
- * @return integer $numWeeks The number of weeks in $year.
- */
- function weeksInYear($year)
- {
- // Find the last Thursday of the year.
- $day = 31;
- $date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0));
- while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) {
- --$date->mday;
- }
- return $date->weekOfYear();
- }
-
- /**
- * Set the date of this object to the $nth weekday of $weekday.
- *
- * @param integer $weekday The day of the week (0 = Sunday, etc).
- * @param integer $nth The $nth $weekday to set to (defaults to 1).
- */
- function setNthWeekday($weekday, $nth = 1)
- {
- if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) {
- return false;
- }
-
- $this->mday = 1;
- $first = $this->dayOfWeek();
- if ($weekday < $first) {
- $this->mday = 8 + $weekday - $first;
- } else {
- $this->mday = $weekday - $first + 1;
- }
- $this->mday += 7 * $nth - 7;
-
- $this->correct();
-
- return true;
- }
-
- function dump($prefix = '')
- {
- echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "<br />\n";
- }
-
- /**
- * Is the date currently represented by this object a valid date?
- *
- * @return boolean Validity, counting leap years, etc.
- */
- function isValid()
- {
- if ($this->year < 0 || $this->year > 9999) {
- return false;
- }
- return checkdate($this->month, $this->mday, $this->year);
- }
-
- /**
- * Correct any over- or underflows in any of the date's members.
- *
- * @param integer $mask We may not want to correct some overflows.
- */
- function correct($mask = HORDE_DATE_MASK_ALLPARTS)
- {
- if ($mask & HORDE_DATE_MASK_SECOND) {
- while ($this->sec < 0) {
- --$this->min;
- $this->sec += 60;
- }
- while ($this->sec > 59) {
- ++$this->min;
- $this->sec -= 60;
- }
- }
-
- if ($mask & HORDE_DATE_MASK_MINUTE) {
- while ($this->min < 0) {
- --$this->hour;
- $this->min += 60;
- }
- while ($this->min > 59) {
- ++$this->hour;
- $this->min -= 60;
- }
- }
-
- if ($mask & HORDE_DATE_MASK_HOUR) {
- while ($this->hour < 0) {
- --$this->mday;
- $this->hour += 24;
- }
- while ($this->hour > 23) {
- ++$this->mday;
- $this->hour -= 24;
- }
- }
-
- if ($mask & HORDE_DATE_MASK_MONTH) {
- while ($this->month > 12) {
- ++$this->year;
- $this->month -= 12;
- }
- while ($this->month < 1) {
- --$this->year;
- $this->month += 12;
- }
- }
-
- if ($mask & HORDE_DATE_MASK_DAY) {
- while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) {
- $this->mday -= Horde_Date::daysInMonth($this->month, $this->year);
- ++$this->month;
- $this->correct(HORDE_DATE_MASK_MONTH);
- }
- while ($this->mday < 1) {
- --$this->month;
- $this->correct(HORDE_DATE_MASK_MONTH);
- $this->mday += Horde_Date::daysInMonth($this->month, $this->year);
- }
- }
- }
-
- /**
- * Compare this date to another date object to see which one is
- * greater (later). Assumes that the dates are in the same
- * timezone.
- *
- * @param mixed $date The date to compare to.
- *
- * @return integer == 0 if the dates are equal
- * >= 1 if this date is greater (later)
- * <= -1 if the other date is greater (later)
- */
- function compareDate($date)
- {
- if (!is_object($date) || !is_a($date, 'Horde_Date')) {
- $date = new Horde_Date($date);
- }
-
- if ($this->year != $date->year) {
- return $this->year - $date->year;
- }
- if ($this->month != $date->month) {
- return $this->month - $date->month;
- }
-
- return $this->mday - $date->mday;
- }
-
- /**
- * Compare this to another date object by time, to see which one
- * is greater (later). Assumes that the dates are in the same
- * timezone.
- *
- * @param mixed $date The date to compare to.
- *
- * @return integer == 0 if the dates are equal
- * >= 1 if this date is greater (later)
- * <= -1 if the other date is greater (later)
- */
- function compareTime($date)
- {
- if (!is_object($date) || !is_a($date, 'Horde_Date')) {
- $date = new Horde_Date($date);
- }
-
- if ($this->hour != $date->hour) {
- return $this->hour - $date->hour;
- }
- if ($this->min != $date->min) {
- return $this->min - $date->min;
- }
-
- return $this->sec - $date->sec;
- }
-
- /**
- * Compare this to another date object, including times, to see
- * which one is greater (later). Assumes that the dates are in the
- * same timezone.
- *
- * @param mixed $date The date to compare to.
- *
- * @return integer == 0 if the dates are equal
- * >= 1 if this date is greater (later)
- * <= -1 if the other date is greater (later)
- */
- function compareDateTime($date)
- {
- if (!is_object($date) || !is_a($date, 'Horde_Date')) {
- $date = new Horde_Date($date);
- }
-
- if ($diff = $this->compareDate($date)) {
- return $diff;
- }
-
- return $this->compareTime($date);
- }
-
- /**
- * Get the time offset for local time zone.
- *
- * @param boolean $colon Place a colon between hours and minutes?
- *
- * @return string Timezone offset as a string in the format +HH:MM.
- */
- function tzOffset($colon = true)
- {
- $secs = $this->format('Z');
-
- if ($secs < 0) {
- $sign = '-';
- $secs = -$secs;
- } else {
- $sign = '+';
- }
- $colon = $colon ? ':' : '';
- $mins = intval(($secs + 30) / 60);
- return sprintf('%s%02d%s%02d',
- $sign, $mins / 60, $colon, $mins % 60);
- }
-
- /**
- * Return the unix timestamp representation of this date.
- *
- * @return integer A unix timestamp.
- */
- function timestamp()
- {
- if (class_exists('DateTime')) {
- return $this->format('U');
- } else {
- return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year);
- }
- }
-
- /**
- * Return the unix timestamp representation of this date, 12:00am.
- *
- * @return integer A unix timestamp.
- */
- function datestamp()
- {
- if (class_exists('DateTime')) {
- $dt = new DateTime();
- $dt->setDate($this->year, $this->month, $this->mday);
- $dt->setTime(0, 0, 0);
- return $dt->format('U');
- } else {
- return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year);
- }
- }
-
- /**
- * Format time using the specifiers available in date() or in the DateTime
- * class' format() method.
- *
- * @since Horde 3.3
- *
- * @param string $format
- *
- * @return string Formatted time.
- */
- function format($format)
- {
- if (class_exists('DateTime')) {
- $dt = new DateTime();
- $dt->setDate($this->year, $this->month, $this->mday);
- $dt->setTime($this->hour, $this->min, $this->sec);
- return $dt->format($format);
- } else {
- return date($format, $this->timestamp());
- }
- }
-
- /**
- * Format time in ISO-8601 format. Works correctly since Horde 3.2.
- *
- * @return string Date and time in ISO-8601 format.
- */
- function iso8601DateTime()
- {
- return $this->rfc3339DateTime() . $this->tzOffset();
- }
-
- /**
- * Format time in RFC 2822 format.
- *
- * @return string Date and time in RFC 2822 format.
- */
- function rfc2822DateTime()
- {
- return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false);
- }
-
- /**
- * Format time in RFC 3339 format.
- *
- * @since Horde 3.1
- *
- * @return string Date and time in RFC 3339 format. The seconds part has
- * been added with Horde 3.2.
- */
- function rfc3339DateTime()
- {
- return $this->format('Y-m-d\TH:i:s');
- }
-
- /**
- * Format time to standard 'ctime' format.
- *
- * @return string Date and time.
- */
- function cTime()
- {
- return $this->format('D M j H:i:s Y');
- }
-
- /**
- * Format date and time using strftime() format.
- *
- * @since Horde 3.1
- *
- * @return string strftime() formatted date and time.
- */
- function strftime($format)
- {
- if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) {
- return strftime($format, $this->timestamp());
- } else {
- return $this->_strftime($format);
- }
- }
-
- /**
- * Format date and time using a limited set of the strftime() format.
- *
- * @return string strftime() formatted date and time.
- */
- function _strftime($format)
- {
- if (preg_match('/%[bBpxX]/', $format)) {
- require_once 'Horde/NLS.php';
- }
-
- return preg_replace(
- array('/%b/e',
- '/%B/e',
- '/%C/e',
- '/%d/e',
- '/%D/e',
- '/%e/e',
- '/%H/e',
- '/%I/e',
- '/%m/e',
- '/%M/e',
- '/%n/',
- '/%p/e',
- '/%R/e',
- '/%S/e',
- '/%t/',
- '/%T/e',
- '/%x/e',
- '/%X/e',
- '/%y/e',
- '/%Y/',
- '/%%/'),
- array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))',
- '$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))',
- '(int)($this->year / 100)',
- 'sprintf(\'%02d\', $this->mday)',
- '$this->_strftime(\'%m/%d/%y\')',
- 'sprintf(\'%2d\', $this->mday)',
- 'sprintf(\'%02d\', $this->hour)',
- 'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))',
- 'sprintf(\'%02d\', $this->month)',
- 'sprintf(\'%02d\', $this->min)',
- "\n",
- '$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))',
- '$this->_strftime(\'%H:%M\')',
- 'sprintf(\'%02d\', $this->sec)',
- "\t",
- '$this->_strftime(\'%H:%M:%S\')',
- '$this->_strftime(NLS::getLangInfo(D_FMT))',
- '$this->_strftime(NLS::getLangInfo(T_FMT))',
- 'substr(sprintf(\'%04d\', $this->year), -2)',
- (int)$this->year,
- '%'),
- $format);
- }
-
- /**
- * mktime() implementation that supports dates outside of 1970-2038,
- * from http://phplens.com/phpeverywhere/adodb_date_library.
- *
- * @TODO remove in Horde 4
- *
- * This does NOT work with pre-1970 daylight saving times.
- *
- * @static
- */
- function _mktime($hr, $min, $sec, $mon = false, $day = false,
- $year = false, $is_dst = false, $is_gmt = false)
- {
- if ($mon === false) {
- return $is_gmt
- ? @gmmktime($hr, $min, $sec)
- : @mktime($hr, $min, $sec);
- }
-
- if ($year > 1901 && $year < 2038 &&
- ($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) {
- return $is_gmt
- ? @gmmktime($hr, $min, $sec, $mon, $day, $year)
- : @mktime($hr, $min, $sec, $mon, $day, $year);
- }
-
- $gmt_different = $is_gmt
- ? 0
- : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0));
-
- $mon = intval($mon);
- $day = intval($day);
- $year = intval($year);
-
- if ($mon > 12) {
- $y = floor($mon / 12);
- $year += $y;
- $mon -= $y * 12;
- } elseif ($mon < 1) {
- $y = ceil((1 - $mon) / 12);
- $year -= $y;
- $mon += $y * 12;
- }
-
- $_day_power = 86400;
- $_hour_power = 3600;
- $_min_power = 60;
-
- $_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
- $_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
-
- $_total_date = 0;
- if ($year >= 1970) {
- for ($a = 1970; $a <= $year; $a++) {
- $leaf = Horde_Date::isLeapYear($a);
- if ($leaf == true) {
- $loop_table = $_month_table_leaf;
- $_add_date = 366;
- } else {
- $loop_table = $_month_table_normal;
- $_add_date = 365;
- }
- if ($a < $year) {
- $_total_date += $_add_date;
- } else {
- for ($b = 1; $b < $mon; $b++) {
- $_total_date += $loop_table[$b];
- }
- }
- }
-
- return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
- }
-
- for ($a = 1969 ; $a >= $year; $a--) {
- $leaf = Horde_Date::isLeapYear($a);
- if ($leaf == true) {
- $loop_table = $_month_table_leaf;
- $_add_date = 366;
- } else {
- $loop_table = $_month_table_normal;
- $_add_date = 365;
- }
- if ($a > $year) {
- $_total_date += $_add_date;
- } else {
- for ($b = 12; $b > $mon; $b--) {
- $_total_date += $loop_table[$b];
- }
- }
- }
-
- $_total_date += $loop_table[$mon] - $day;
- $_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
- $_day_time = $_day_power - $_day_time;
- $ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
- if ($ret < -12220185600) {
- // If earlier than 5 Oct 1582 - gregorian correction.
- return $ret + 10 * 86400;
- } elseif ($ret < -12219321600) {
- // If in limbo, reset to 15 Oct 1582.
- return -12219321600;
- } else {
- return $ret;
- }
- }
-
-}
-
-
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+require_once(dirname(__FILE__) . '/Horde_Date.php');
// {{{ Header
/**
* Calculates, manipulates and retrieves dates
*
* It does not rely on 32-bit system time stamps, so it works dates
* before 1970 and after 2038.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor,
* C.A. Woodcock
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of the BSD License.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @category Date and Time
* @package Date
* @author Monte Ohrt <monte@ispi.net>
* @author Pierre-Alain Joye <pajoye@php.net>
* @author Daniel Convissor <danielc@php.net>
* @author C.A. Woodcock <c01234@netcomuk.co.uk>
* @copyright 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor, C.A. Woodcock
* @license http://www.opensource.org/licenses/bsd-license.php
* BSD License
* @version CVS: $Id: Calc.php,v 1.57 2008/03/23 18:34:16 c01234 Exp $
* @link http://pear.php.net/package/Date
* @since File available since Release 1.2
*/
// }}}
// {{{ General constants:
if (!defined('DATE_CALC_BEGIN_WEEKDAY')) {
/**
* Defines what day starts the week
*
* Monday (1) is the international standard.
* Redefine this to 0 if you want weeks to begin on Sunday.
*/
define('DATE_CALC_BEGIN_WEEKDAY', 1);
}
if (!defined('DATE_CALC_FORMAT')) {
/**
* The default value for each method's $format parameter
*
* The default is '%Y%m%d'. To override this default, define
* this constant before including Calc.php.
*
* @since Constant available since Release 1.4.4
*/
define('DATE_CALC_FORMAT', '%Y%m%d');
}
// {{{ Date precision constants (used in 'round()' and 'trunc()'):
define('DATE_PRECISION_YEAR', -2);
define('DATE_PRECISION_MONTH', -1);
define('DATE_PRECISION_DAY', 0);
define('DATE_PRECISION_HOUR', 1);
define('DATE_PRECISION_10MINUTES', 2);
define('DATE_PRECISION_MINUTE', 3);
define('DATE_PRECISION_10SECONDS', 4);
define('DATE_PRECISION_SECOND', 5);
// }}}
// {{{ Class: Date_Calc
/**
* Calculates, manipulates and retrieves dates
*
* It does not rely on 32-bit system time stamps, so it works dates
* before 1970 and after 2038.
*
* @category Date and Time
* @package Date
* @author Monte Ohrt <monte@ispi.net>
* @author Daniel Convissor <danielc@php.net>
* @author C.A. Woodcock <c01234@netcomuk.co.uk>
* @copyright 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor, C.A. Woodcock
* @license http://www.opensource.org/licenses/bsd-license.php
* BSD License
* @version Release: 1.5.0a1
* @link http://pear.php.net/package/Date
* @since Class available since Release 1.2
*/
class Date_Calc
{
// {{{ dateFormat()
/**
* Formats the date in the given format, much like strfmt()
*
* This function is used to alleviate the problem with 32-bit numbers for
* dates pre 1970 or post 2038, as strfmt() has on most systems.
* Most of the formatting options are compatible.
*
* Formatting options:
* <pre>
* %a abbreviated weekday name (Sun, Mon, Tue)
* %A full weekday name (Sunday, Monday, Tuesday)
* %b abbreviated month name (Jan, Feb, Mar)
* %B full month name (January, February, March)
* %d day of month (range 00 to 31)
* %e day of month, single digit (range 0 to 31)
* %E number of days since unspecified epoch (integer)
* (%E is useful for passing a date in a URL as
* an integer value. Then simply use
* daysToDate() to convert back to a date.)
* %j day of year (range 001 to 366)
* %m month as decimal number (range 1 to 12)
* %n newline character (\n)
* %t tab character (\t)
* %w weekday as decimal (0 = Sunday)
* %U week number of current year, first sunday as first week
* %y year as decimal (range 00 to 99)
* %Y year as decimal including century (range 0000 to 9999)
* %% literal '%'
* </pre>
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
* @param string $format the format string
*
* @return string the date in the desired format
* @access public
* @static
*/
function dateFormat($day, $month, $year, $format)
{
if (!Date_Calc::isValidDate($day, $month, $year)) {
$year = Date_Calc::dateNow('%Y');
$month = Date_Calc::dateNow('%m');
$day = Date_Calc::dateNow('%d');
}
$output = '';
for ($strpos = 0; $strpos < strlen($format); $strpos++) {
$char = substr($format, $strpos, 1);
if ($char == '%') {
$nextchar = substr($format, $strpos + 1, 1);
switch($nextchar) {
case 'a':
$output .= Date_Calc::getWeekdayAbbrname($day, $month, $year);
break;
case 'A':
$output .= Date_Calc::getWeekdayFullname($day, $month, $year);
break;
case 'b':
$output .= Date_Calc::getMonthAbbrname($month);
break;
case 'B':
$output .= Date_Calc::getMonthFullname($month);
break;
case 'd':
$output .= sprintf('%02d', $day);
break;
case 'e':
$output .= $day;
break;
case 'E':
$output .= Date_Calc::dateToDays($day, $month, $year);
break;
case 'j':
$output .= Date_Calc::dayOfYear($day, $month, $year);
break;
case 'm':
$output .= sprintf('%02d', $month);
break;
case 'n':
$output .= "\n";
break;
case 't':
$output .= "\t";
break;
case 'w':
$output .= Date_Calc::dayOfWeek($day, $month, $year);
break;
case 'U':
$output .= Date_Calc::weekOfYear($day, $month, $year);
break;
case 'y':
$output .= sprintf('%0' .
($year < 0 ? '3' : '2') .
'd',
$year % 100);
break;
case "Y":
$output .= sprintf('%0' .
($year < 0 ? '5' : '4') .
'd',
$year);
break;
case '%':
$output .= '%';
break;
default:
$output .= $char.$nextchar;
}
$strpos++;
} else {
$output .= $char;
}
}
return $output;
}
// }}}
// {{{ dateNow()
/**
* Returns the current local date
*
* NOTE: This function retrieves the local date using strftime(),
* which may or may not be 32-bit safe on your system.
*
* @param string $format the string indicating how to format the output
*
* @return string the current date in the specified format
* @access public
* @static
*/
function dateNow($format = DATE_CALC_FORMAT)
{
return strftime($format, time());
}
// }}}
// {{{ getYear()
/**
* Returns the current local year in format CCYY
*
* @return string the current year in four digit format
* @access public
* @static
*/
function getYear()
{
return Date_Calc::dateNow('%Y');
}
// }}}
// {{{ getMonth()
/**
* Returns the current local month in format MM
*
* @return string the current month in two digit format
* @access public
* @static
*/
function getMonth()
{
return Date_Calc::dateNow('%m');
}
// }}}
// {{{ getDay()
/**
* Returns the current local day in format DD
*
* @return string the current day of the month in two digit format
* @access public
* @static
*/
function getDay()
{
return Date_Calc::dateNow('%d');
}
// }}}
// {{{ defaultCentury()
/**
* Turns a two digit year into a four digit year
*
* Return value depends on current year; the century chosen
* will be the one which forms the year that is closest
* to the current year. If the two possibilities are
* equidistant to the current year (i.e. 50 years in the past
* and 50 years in the future), then the past year is chosen.
*
* For example, if the current year is 2007:
* 03 - returns 2003
* 09 - returns 2009
* 56 - returns 2056 (closer to 2007 than 1956)
* 57 - returns 1957 (1957 and 2007 are equidistant, so previous century
* chosen)
* 58 - returns 1958
*
* @param int $year the 2 digit year
*
* @return int the 4 digit year
* @access public
* @static
*/
function defaultCentury($year)
{
$hn_century = intval(($hn_currentyear = date("Y")) / 100);
$hn_currentyear = $hn_currentyear % 100;
if ($year < 0 || $year >= 100)
$year = $year % 100;
if ($year - $hn_currentyear < -50)
return ($hn_century + 1) * 100 + $year;
else if ($year - $hn_currentyear < 50)
return $hn_century * 100 + $year;
else
return ($hn_century - 1) * 100 + $year;
}
// }}}
// {{{ getSecondsInYear()
/**
* Returns the total number of seconds in the given year
*
* This takes into account leap seconds.
*
* @param int $pn_year the year in four digit format
*
* @return int
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getSecondsInYear($pn_year)
{
$pn_year = intval($pn_year);
static $ha_leapseconds;
if (!isset($ha_leapseconds)) {
$ha_leapseconds = array(1972 => 2,
1973 => 1,
1974 => 1,
1975 => 1,
1976 => 1,
1977 => 1,
1978 => 1,
1979 => 1,
1981 => 1,
1982 => 1,
1983 => 1,
1985 => 1,
1987 => 1,
1989 => 1,
1990 => 1,
1992 => 1,
1993 => 1,
1994 => 1,
1995 => 1,
1997 => 1,
1998 => 1,
2005 => 1);
}
$ret = Date_Calc::daysInYear($pn_year) * 86400;
if (isset($ha_leapseconds[$pn_year])) {
return $ret + $ha_leapseconds[$pn_year];
} else {
return $ret;
}
}
// }}}
// {{{ getSecondsInMonth()
/**
* Returns the total number of seconds in the given month
*
* This takes into account leap seconds.
*
* @param int $pn_month the month
* @param int $pn_year the year in four digit format
*
* @return int
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getSecondsInMonth($pn_month, $pn_year)
{
$pn_month = intval($pn_month);
$pn_year = intval($pn_year);
static $ha_leapseconds;
if (!isset($ha_leapseconds)) {
$ha_leapseconds = array(1972 => array(6 => 1,
12 => 1),
1973 => array(12 => 1),
1974 => array(12 => 1),
1975 => array(12 => 1),
1976 => array(12 => 1),
1977 => array(12 => 1),
1978 => array(12 => 1),
1979 => array(12 => 1),
1981 => array(6 => 1),
1982 => array(6 => 1),
1983 => array(6 => 1),
1985 => array(6 => 1),
1987 => array(12 => 1),
1989 => array(12 => 1),
1990 => array(12 => 1),
1992 => array(6 => 1),
1993 => array(6 => 1),
1994 => array(6 => 1),
1995 => array(12 => 1),
1997 => array(6 => 1),
1998 => array(12 => 1),
2005 => array(12 => 1));
}
$ret = Date_Calc::daysInMonth($pn_month, $pn_year) * 86400;
if (isset($ha_leapseconds[$pn_year][$pn_month])) {
return $ret + $ha_leapseconds[$pn_year][$pn_month];
} else {
return $ret;
}
}
// }}}
// {{{ getSecondsInDay()
/**
* Returns the total number of seconds in the day of the given date
*
* This takes into account leap seconds.
*
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year in four digit format
*
* @return int
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getSecondsInDay($pn_day, $pn_month, $pn_year)
{
// Note to developers:
//
// The leap seconds listed here are a matter of historical fact,
// that is, it is known on which exact day they occurred.
// However, the implementation of the class as a whole depends
// on the fact that they always occur at the end of the month
// (although it is assumed that they could occur in any month,
// even though practically they only occur in June or December).
//
// Do not define a leap second on a day of the month other than
// the last day without altering the implementation of the
// functions that depend on this one.
//
// It is possible, though, to define an un-leap second (i.e. a skipped
// second (I do not know what they are called), or a number of
// consecutive leap seconds).
$pn_day = intval($pn_day);
$pn_month = intval($pn_month);
$pn_year = intval($pn_year);
static $ha_leapseconds;
if (!isset($ha_leapseconds)) {
$ha_leapseconds = array(1972 => array(6 => array(30 => 1),
12 => array(31 => 1)),
1973 => array(12 => array(31 => 1)),
1974 => array(12 => array(31 => 1)),
1975 => array(12 => array(31 => 1)),
1976 => array(12 => array(31 => 1)),
1977 => array(12 => array(31 => 1)),
1978 => array(12 => array(31 => 1)),
1979 => array(12 => array(31 => 1)),
1981 => array(6 => array(30 => 1)),
1982 => array(6 => array(30 => 1)),
1983 => array(6 => array(30 => 1)),
1985 => array(6 => array(30 => 1)),
1987 => array(12 => array(31 => 1)),
1989 => array(12 => array(31 => 1)),
1990 => array(12 => array(31 => 1)),
1992 => array(6 => array(30 => 1)),
1993 => array(6 => array(30 => 1)),
1994 => array(6 => array(30 => 1)),
1995 => array(12 => array(31 => 1)),
1997 => array(6 => array(30 => 1)),
1998 => array(12 => array(31 => 1)),
2005 => array(12 => array(31 => 1)));
}
if (isset($ha_leapseconds[$pn_year][$pn_month][$pn_day])) {
return 86400 + $ha_leapseconds[$pn_year][$pn_month][$pn_day];
} else {
return 86400;
}
}
// }}}
// {{{ getSecondsInHour()
/**
* Returns the total number of seconds in the hour of the given date
*
* This takes into account leap seconds.
*
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year in four digit format
* @param int $pn_hour the hour
*
* @return int
* @access public
* @static
*/
function getSecondsInHour($pn_day, $pn_month, $pn_year, $pn_hour)
{
if ($pn_hour < 23)
return 3600;
else
return Date_Calc::getSecondsInDay($pn_day, $pn_month, $pn_year) -
82800;
}
// }}}
// {{{ getSecondsInMinute()
/**
* Returns the total number of seconds in the minute of the given hour
*
* This takes into account leap seconds.
*
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year in four digit format
* @param int $pn_hour the hour
* @param int $pn_minute the minute
*
* @return int
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getSecondsInMinute($pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute)
{
if ($pn_hour < 23 || $pn_minute < 59)
return 60;
else
return Date_Calc::getSecondsInDay($pn_day, $pn_month, $pn_year) -
86340;
}
// }}}
// {{{ secondsPastMidnight()
/**
* Returns the no of seconds since midnight (0-86399)
*
* @param int $pn_hour the hour of the day
* @param int $pn_minute the minute
* @param mixed $pn_second the second as integer or float
*
* @return mixed integer or float from 0-86399
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function secondsPastMidnight($pn_hour, $pn_minute, $pn_second)
{
return 3600 * $pn_hour + 60 * $pn_minute + $pn_second;
}
// }}}
// {{{ secondsPastMidnightToTime()
/**
* Returns the time as an array (i.e. hour, minute, second)
*
* @param mixed $pn_seconds the no of seconds since midnight (0-86399)
*
* @return mixed array of hour, minute (both as integers), second (as
* integer or float, depending on parameter)
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function secondsPastMidnightToTime($pn_seconds)
{
if ($pn_seconds >= 86400) {
return array(23, 59, $pn_seconds - 86340);
}
$hn_hour = intval($pn_seconds / 3600);
$hn_minute = intval(($pn_seconds - $hn_hour * 3600) / 60);
$hn_second = is_float($pn_seconds) ?
fmod($pn_seconds, 60) :
$pn_seconds % 60;
return array($hn_hour, $hn_minute, $hn_second);
}
// }}}
// {{{ secondsPastTheHour()
/**
* Returns the no of seconds since the last hour o'clock (0-3599)
*
* @param int $pn_minute the minute
* @param mixed $pn_second the second as integer or float
*
* @return mixed integer or float from 0-3599
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function secondsPastTheHour($pn_minute, $pn_second)
{
return 60 * $pn_minute + $pn_second;
}
// }}}
// {{{ addHours()
/**
* Returns the date the specified no of hours from the given date
*
* To subtract hours use a negative value for the '$pn_hours' parameter
*
* @param int $pn_hours hours to add
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
*
* @return array array of year, month, day, hour
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addHours($pn_hours, $pn_day, $pn_month, $pn_year, $pn_hour)
{
if ($pn_hours == 0)
return array((int) $pn_year,
(int) $pn_month,
(int) $pn_day,
(int) $pn_hour);
$hn_days = intval($pn_hours / 24);
$hn_hour = $pn_hour + $pn_hours % 24;
if ($hn_hour >= 24) {
++$hn_days;
$hn_hour -= 24;
} else if ($hn_hour < 0) {
--$hn_days;
$hn_hour += 24;
}
if ($hn_days == 0) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
} else {
list($hn_year, $hn_month, $hn_day) =
explode(" ",
Date_Calc::addDays($hn_days,
$pn_day,
$pn_month,
$pn_year,
"%Y %m %d"));
}
return array((int) $hn_year, (int) $hn_month, (int) $hn_day, $hn_hour);
}
// }}}
// {{{ addMinutes()
/**
* Returns the date the specified no of minutes from the given date
*
* To subtract minutes use a negative value for the '$pn_minutes' parameter
*
* @param int $pn_minutes minutes to add
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
*
* @return array array of year, month, day, hour, minute
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addMinutes($pn_minutes,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute)
{
if ($pn_minutes == 0)
return array((int) $pn_year,
(int) $pn_month,
(int) $pn_day,
(int) $pn_hour,
(int) $pn_minute);
$hn_hours = intval($pn_minutes / 60);
$hn_minute = $pn_minute + $pn_minutes % 60;
if ($hn_minute >= 60) {
++$hn_hours;
$hn_minute -= 60;
} else if ($hn_minute < 0) {
--$hn_hours;
$hn_minute += 60;
}
if ($hn_hours == 0) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
$hn_hour = $pn_hour;
} else {
list($hn_year, $hn_month, $hn_day, $hn_hour) =
Date_Calc::addHours($hn_hours,
$pn_day,
$pn_month,
$pn_year,
$pn_hour);
}
return array($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute);
}
// }}}
// {{{ addSeconds()
/**
* Returns the date the specified no of seconds from the given date
*
* If leap seconds are specified to be counted, the passed time must be UTC.
* To subtract seconds use a negative value for the '$pn_seconds' parameter.
*
* N.B. the return type of the second part of the date is float if
* either '$pn_seconds' or '$pn_second' is a float; otherwise, it
* is integer.
*
* @param mixed $pn_seconds seconds to add as integer or float
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param mixed $pn_second the second as integer or float
* @param bool $pb_countleap whether to count leap seconds (defaults to
* DATE_COUNT_LEAP_SECONDS)
*
* @return array array of year, month, day, hour, minute, second
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addSeconds($pn_seconds,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute,
$pn_second,
$pb_countleap = DATE_COUNT_LEAP_SECONDS)
{
if ($pn_seconds == 0)
return array((int) $pn_year,
(int) $pn_month,
(int) $pn_day,
(int) $pn_hour,
(int) $pn_minute,
$pn_second);
if ($pb_countleap) {
$hn_seconds = $pn_seconds;
$hn_day = (int) $pn_day;
$hn_month = (int) $pn_month;
$hn_year = (int) $pn_year;
$hn_hour = (int) $pn_hour;
$hn_minute = (int) $pn_minute;
$hn_second = $pn_second;
$hn_days = Date_Calc::dateToDays($pn_day,
$pn_month,
$pn_year);
$hn_secondsofmonth = 86400 * ($hn_days -
Date_Calc::firstDayOfMonth($pn_month,
$pn_year)) +
Date_Calc::secondsPastMidnight($pn_hour,
$pn_minute,
$pn_second);
if ($hn_seconds > 0) {
// Advance to end of month:
//
if ($hn_secondsofmonth != 0 &&
$hn_secondsofmonth + $hn_seconds >=
($hn_secondsinmonth =
Date_Calc::getSecondsInMonth($hn_month, $hn_year))) {
$hn_seconds -= $hn_secondsinmonth - $hn_secondsofmonth;
$hn_secondsofmonth = 0;
list($hn_year, $hn_month) =
Date_Calc::nextMonth($hn_month, $hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
$hn_hour = $hn_minute = $hn_second = 0;
}
// Advance to end of year:
//
if ($hn_secondsofmonth == 0 &&
$hn_month != Date_Calc::getFirstMonthOfYear($hn_year)) {
while ($hn_year == $pn_year &&
$hn_seconds >= ($hn_secondsinmonth =
Date_Calc::getSecondsInMonth($hn_month,
$hn_year))) {
$hn_seconds -= $hn_secondsinmonth;
list($hn_year, $hn_month) =
Date_Calc::nextMonth($hn_month, $hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
}
}
if ($hn_secondsofmonth == 0) {
// Add years:
//
if ($hn_month == Date_Calc::getFirstMonthOfYear($hn_year)) {
while ($hn_seconds >= ($hn_secondsinyear =
Date_Calc::getSecondsInYear($hn_year))) {
$hn_seconds -= $hn_secondsinyear;
$hn_month = Date_Calc::getFirstMonthOfYear(++$hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
}
}
// Add months:
//
while ($hn_seconds >= ($hn_secondsinmonth =
Date_Calc::getSecondsInMonth($hn_month, $hn_year))) {
$hn_seconds -= $hn_secondsinmonth;
list($hn_year, $hn_month) =
Date_Calc::nextMonth($hn_month, $hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year);
}
}
} else {
//
// (if $hn_seconds < 0)
// Go back to start of month:
//
if ($hn_secondsofmonth != 0 &&
-$hn_seconds >= $hn_secondsofmonth) {
$hn_seconds += $hn_secondsofmonth;
$hn_secondsofmonth = 0;
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
$hn_hour = $hn_minute = $hn_second = 0;
}
// Go back to start of year:
//
if ($hn_secondsofmonth == 0) {
while ($hn_month !=
Date_Calc::getFirstMonthOfYear($hn_year)) {
list($hn_year, $hn_prevmonth) =
Date_Calc::prevMonth($hn_month, $hn_year);
if (-$hn_seconds >= ($hn_secondsinmonth =
Date_Calc::getSecondsInMonth($hn_prevmonth,
$hn_year))) {
$hn_seconds += $hn_secondsinmonth;
$hn_month = $hn_prevmonth;
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
} else {
break;
}
}
}
if ($hn_secondsofmonth == 0) {
// Subtract years:
//
if ($hn_month == Date_Calc::getFirstMonthOfYear($hn_year)) {
while (-$hn_seconds >= ($hn_secondsinyear =
Date_Calc::getSecondsInYear($hn_year - 1))) {
$hn_seconds += $hn_secondsinyear;
$hn_month = Date_Calc::getFirstMonthOfYear(--$hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
}
}
// Subtract months:
//
list($hn_pmyear, $hn_prevmonth) =
Date_Calc::prevMonth($hn_month, $hn_year);
while (-$hn_seconds >= ($hn_secondsinmonth =
Date_Calc::getSecondsInMonth($hn_prevmonth,
$hn_pmyear))) {
$hn_seconds += $hn_secondsinmonth;
$hn_year = $hn_pmyear;
$hn_month = $hn_prevmonth;
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month,
$hn_year);
list($hn_pmyear, $hn_prevmonth) =
Date_Calc::prevMonth($hn_month, $hn_year);
}
}
}
if ($hn_seconds < 0 && $hn_secondsofmonth == 0) {
list($hn_year, $hn_month) =
Date_Calc::prevMonth($hn_month, $hn_year);
$hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year);
$hn_seconds += Date_Calc::getSecondsInMonth($hn_month, $hn_year);
}
$hn_seconds += Date_Calc::secondsPastMidnight($hn_hour,
$hn_minute,
$hn_second);
if ($hn_seconds < 0) {
$hn_daysadd = intval($hn_seconds / 86400) - 1;
} else if ($hn_seconds < 86400) {
$hn_daysadd = 0;
} else {
$hn_daysadd = intval($hn_seconds / 86400) - 1;
}
if ($hn_daysadd != 0) {
list($hn_year, $hn_month, $hn_day) =
explode(" ",
Date_Calc::addDays($hn_daysadd,
$hn_day,
$hn_month,
$hn_year,
"%Y %m %d"));
$hn_seconds -= $hn_daysadd * 86400;
}
$hn_secondsinday = Date_Calc::getSecondsInDay($hn_day,
$hn_month,
$hn_year);
if ($hn_seconds >= $hn_secondsinday) {
list($hn_year, $hn_month, $hn_day) =
explode(" ",
Date_Calc::addDays(1,
$hn_day,
$hn_month,
$hn_year,
"%Y %m %d"));
$hn_seconds -= $hn_secondsinday;
}
list($hn_hour, $hn_minute, $hn_second) =
Date_Calc::secondsPastMidnightToTime($hn_seconds);
return array((int) $hn_year,
(int) $hn_month,
(int) $hn_day,
$hn_hour,
$hn_minute,
$hn_second);
} else {
// Assume every day has 86400 seconds exactly (ignore leap seconds):
//
$hn_minutes = intval($pn_seconds / 60);
if (is_float($pn_seconds)) {
$hn_second = $pn_second + fmod($pn_seconds, 60);
} else {
$hn_second = $pn_second + $pn_seconds % 60;
}
if ($hn_second >= 60) {
++$hn_minutes;
$hn_second -= 60;
} else if ($hn_second < 0) {
--$hn_minutes;
$hn_second += 60;
}
if ($hn_minutes == 0) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
$hn_hour = $pn_hour;
$hn_minute = $pn_minute;
} else {
list($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute) =
Date_Calc::addMinutes($hn_minutes,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute);
}
return array($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute,
$hn_second);
}
}
// }}}
// {{{ dateToDays()
/**
* Converts a date in the proleptic Gregorian calendar to the no of days
* since 24th November, 4714 B.C.
*
* Returns the no of days since Monday, 24th November, 4714 B.C. in the
* proleptic Gregorian calendar (which is 24th November, -4713 using
* 'Astronomical' year numbering, and 1st January, 4713 B.C. in the
* proleptic Julian calendar). This is also the first day of the 'Julian
* Period' proposed by Joseph Scaliger in 1583, and the number of days
* since this date is known as the 'Julian Day'. (It is not directly
* to do with the Julian calendar, although this is where the name
* is derived from.)
*
* The algorithm is valid for all years (positive and negative), and
* also for years preceding 4714 B.C.
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year (using 'Astronomical' year numbering)
*
* @return int the number of days since 24th November, 4714 B.C.
* @access public
* @static
*/
function dateToDays($day, $month, $year)
{
if ($month > 2) {
// March = 0, April = 1, ..., December = 9,
// January = 10, February = 11
$month -= 3;
} else {
$month += 9;
--$year;
}
$hb_negativeyear = $year < 0;
$century = intval($year / 100);
$year = $year % 100;
if ($hb_negativeyear) {
// Subtract 1 because year 0 is a leap year;
// And N.B. that we must treat the leap years as occurring
// one year earlier than they do, because for the purposes
// of calculation, the year starts on 1st March:
//
return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) +
intval((1461 * $year + 1) / 4) +
intval((153 * $month + 2) / 5) +
$day + 1721118;
} else {
return intval(146097 * $century / 4) +
intval(1461 * $year / 4) +
intval((153 * $month + 2) / 5) +
$day + 1721119;
}
}
// }}}
// {{{ daysToDate()
/**
* Converts no of days since 24th November, 4714 B.C. (in the proleptic
* Gregorian calendar, which is year -4713 using 'Astronomical' year
* numbering) to Gregorian calendar date
*
* Returned date belongs to the proleptic Gregorian calendar, using
* 'Astronomical' year numbering.
*
* The algorithm is valid for all years (positive and negative), and
* also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
* and so the only limitation is platform-dependent (for 32-bit systems
* the maximum year would be something like about 1,465,190 A.D.).
*
* N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
*
* @param int $days the number of days since 24th November, 4714 B.C.
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function daysToDate($days, $format = DATE_CALC_FORMAT)
{
$days = intval($days);
$days -= 1721119;
$century = floor((4 * $days - 1) / 146097);
$days = floor(4 * $days - 1 - 146097 * $century);
$day = floor($days / 4);
$year = floor((4 * $day + 3) / 1461);
$day = floor(4 * $day + 3 - 1461 * $year);
$day = floor(($day + 4) / 4);
$month = floor((5 * $day - 3) / 153);
$day = floor(5 * $day - 3 - 153 * $month);
$day = floor(($day + 5) / 5);
$year = $century * 100 + $year;
if ($month < 10) {
$month +=3;
} else {
$month -=9;
++$year;
}
return Date_Calc::dateFormat($day, $month, $year, $format);
}
// }}}
// {{{ getMonths()
/**
* Returns array of the month numbers, in order, for the given year
*
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return array array of integer month numbers, in order
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getMonths($pn_year)
{
// N.B. Month numbers can be skipped but not duplicated:
//
return array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
}
// }}}
// {{{ getMonthNames()
/**
* Returns an array of month names
*
* Used to take advantage of the setlocale function to return
* language specific month names.
*
* TODO: cache values to some global array to avoid performance
* hits when called more than once.
*
* @param int $pb_abbreviated whether to return the abbreviated form of the
* months
*
* @return array associative array of integer month numbers, in
* order, to month names
* @access public
* @static
*/
function getMonthNames($pb_abbreviated = false)
{
$ret = array();
foreach (Date_Calc::getMonths(2001) as $i) {
$ret[$i] = strftime($pb_abbreviated ? '%b' : '%B',
mktime(0, 0, 0, $i, 1, 2001));
}
return $ret;
}
// }}}
// {{{ prevMonth()
/**
* Returns month and year of previous month
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return array array of year, month as integers
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function prevMonth($pn_month, $pn_year)
{
$ha_months = Date_Calc::getMonths($pn_year);
$hn_monthkey = array_search($pn_month, $ha_months);
if (array_key_exists($hn_monthkey - 1, $ha_months)) {
return array((int) $pn_year, $ha_months[$hn_monthkey - 1]);
} else {
$ha_months = Date_Calc::getMonths($pn_year - 1);
return array($pn_year - 1, end($ha_months));
}
}
// }}}
// {{{ nextMonth()
/**
* Returns month and year of next month
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return array array of year, month as integers
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function nextMonth($pn_month, $pn_year)
{
$ha_months = Date_Calc::getMonths($pn_year);
$hn_monthkey = array_search($pn_month, $ha_months);
if (array_key_exists($hn_monthkey + 1, $ha_months)) {
return array((int) $pn_year, $ha_months[$hn_monthkey + 1]);
} else {
$ha_months = Date_Calc::getMonths($pn_year + 1);
return array($pn_year + 1, $ha_months[0]);
}
}
// }}}
// {{{ addMonthsToDays()
/**
* Returns 'Julian Day' of the date the specified no of months
* from the given date
*
* To subtract months use a negative value for the '$pn_months'
* parameter
*
* @param int $pn_months months to add
* @param int $pn_days 'Julian Day', i.e. the no of days since 1st
* January, 4713 B.C.
*
* @return int 'Julian Day', i.e. the no of days since 1st January,
* 4713 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addMonthsToDays($pn_months, $pn_days)
{
if ($pn_months == 0)
return (int) $pn_days;
list($hn_year, $hn_month, $hn_day) =
explode(" ", Date_Calc::daysToDate($pn_days, "%Y %m %d"));
$hn_retmonth = $hn_month + $pn_months % 12;
$hn_retyear = $hn_year + intval($pn_months / 12);
if ($hn_retmonth < 1) {
$hn_retmonth += 12;
--$hn_retyear;
} else if ($hn_retmonth > 12) {
$hn_retmonth -= 12;
++$hn_retyear;
}
if (Date_Calc::isValidDate($hn_day, $hn_retmonth, $hn_retyear))
return Date_Calc::dateToDays($hn_day, $hn_retmonth, $hn_retyear);
// Calculate days since first of month:
//
$hn_dayoffset = $pn_days -
Date_Calc::firstDayOfMonth($hn_month, $hn_year);
$hn_retmonthfirstday = Date_Calc::firstDayOfMonth($hn_retmonth,
$hn_retyear);
$hn_retmonthlastday = Date_Calc::lastDayOfMonth($hn_retmonth,
$hn_retyear);
if ($hn_dayoffset > $hn_retmonthlastday - $hn_retmonthfirstday) {
return $hn_retmonthlastday;
} else {
return $hn_retmonthfirstday + $hn_dayoffset;
}
}
// }}}
// {{{ addMonths()
/**
* Returns the date the specified no of months from the given date
*
* To subtract months use a negative value for the '$pn_months'
* parameter
*
* @param int $pn_months months to add
* @param int $pn_day the day of the month, default is current local
* day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param string $ps_format string specifying how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addMonths($pn_months,
$pn_day,
$pn_month,
$pn_year,
$ps_format = DATE_CALC_FORMAT)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
if ($pn_months == 0)
return Date_Calc::dateFormat($pn_day,
$pn_month,
$pn_year,
$ps_format);
$hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year);
return Date_Calc::daysToDate(Date_Calc::addMonthsToDays($pn_months,
$hn_days),
$ps_format);
}
// }}}
// {{{ addYearsToDays()
/**
* Returns 'Julian Day' of the date the specified no of years
* from the given date
*
* To subtract years use a negative value for the '$pn_years'
* parameter
*
* @param int $pn_years years to add
* @param int $pn_days 'Julian Day', i.e. the no of days since 1st January,
* 4713 B.C.
*
* @return int 'Julian Day', i.e. the no of days since 1st January,
* 4713 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addYearsToDays($pn_years, $pn_days)
{
if ($pn_years == 0)
return (int) $pn_days;
list($hn_year, $hn_month, $hn_day) =
explode(" ", Date_Calc::daysToDate($pn_days, "%Y %m %d"));
$hn_retyear = $hn_year + $pn_years;
if (Date_Calc::isValidDate($hn_day, $hn_month, $hn_retyear))
return Date_Calc::dateToDays($hn_day, $hn_month, $hn_retyear);
$ha_months = Date_Calc::getMonths($hn_retyear);
if (in_array($hn_month, $ha_months)) {
$hn_retmonth = $hn_month;
// Calculate days since first of month:
//
$hn_dayoffset = $pn_days - Date_Calc::firstDayOfMonth($hn_month,
$hn_year);
$hn_retmonthfirstday = Date_Calc::firstDayOfMonth($hn_retmonth,
$hn_retyear);
$hn_retmonthlastday = Date_Calc::lastDayOfMonth($hn_retmonth,
$hn_retyear);
if ($hn_dayoffset > $hn_retmonthlastday - $hn_retmonthfirstday) {
return $hn_retmonthlastday;
} else {
return $hn_retmonthfirstday + $hn_dayoffset;
}
} else {
// Calculate days since first of year:
//
$hn_dayoffset = $pn_days - Date_Calc::firstDayOfYear($hn_year);
$hn_retyearfirstday = Date_Calc::firstDayOfYear($hn_retyear);
$hn_retyearlastday = Date_Calc::lastDayOfYear($hn_retyear);
if ($hn_dayoffset > $hn_retyearlastday - $hn_retyearfirstday) {
return $hn_retyearlastday;
} else {
return $hn_retyearfirstday + $hn_dayoffset;
}
}
}
// }}}
// {{{ addYears()
/**
* Returns the date the specified no of years from the given date
*
* To subtract years use a negative value for the '$pn_years'
* parameter
*
* @param int $pn_years years to add
* @param int $pn_day the day of the month, default is current local
* day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param string $ps_format string specifying how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addYears($pn_years,
$pn_day,
$pn_month,
$pn_year,
$ps_format = DATE_CALC_FORMAT)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
if ($pn_years == 0)
return Date_Calc::dateFormat($pn_day,
$pn_month,
$pn_year,
$ps_format);
$hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year);
return Date_Calc::daysToDate(Date_Calc::addYearsToDays($pn_years,
$hn_days),
$ps_format);
}
// }}}
// {{{ addDays()
/**
* Returns the date the specified no of days from the given date
*
* To subtract days use a negative value for the '$pn_days' parameter
*
* @param int $pn_days days to add
* @param int $pn_day the day of the month, default is current local
* day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param string $ps_format string specifying how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function addDays($pn_days,
$pn_day,
$pn_month,
$pn_year,
$ps_format = DATE_CALC_FORMAT)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
if ($pn_days == 0)
return Date_Calc::dateFormat($pn_day,
$pn_month,
$pn_year,
$ps_format);
return Date_Calc::daysToDate(Date_Calc::dateToDays($pn_day,
$pn_month,
$pn_year) +
$pn_days,
$ps_format);
}
// }}}
// {{{ getFirstDayOfMonth()
/**
* Returns first day of the specified month of specified year as integer
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return int number of first day of month
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getFirstDayOfMonth($pn_month, $pn_year)
{
return 1;
}
// }}}
// {{{ getLastDayOfMonth()
/**
* Returns last day of the specified month of specified year as integer
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return int number of last day of month
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getLastDayOfMonth($pn_month, $pn_year)
{
return Date_Calc::daysInMonth($pn_month, $pn_year);
}
// }}}
// {{{ firstDayOfMonth()
/**
* Returns the Julian Day of the first day of the month of the specified
* year (i.e. the no of days since 24th November, 4714 B.C.)
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return integer the number of days since 24th November, 4714 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function firstDayOfMonth($pn_month, $pn_year)
{
return Date_Calc::dateToDays(Date_Calc::getFirstDayOfMonth($pn_month,
$pn_year),
$pn_month,
$pn_year);
}
// }}}
// {{{ lastDayOfMonth()
/**
* Returns the Julian Day of the last day of the month of the specified
* year (i.e. the no of days since 24th November, 4714 B.C.)
*
* @param int $pn_month the month
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return integer the number of days since 24th November, 4714 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function lastDayOfMonth($pn_month, $pn_year)
{
list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($pn_month,
$pn_year);
return Date_Calc::firstDayOfMonth($hn_nextmonth, $hn_nmyear) - 1;
}
// }}}
// {{{ getFirstMonthOfYear()
/**
* Returns first month of specified year as integer
*
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return int number of first month of year
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function getFirstMonthOfYear($pn_year)
{
$ha_months = Date_Calc::getMonths($pn_year);
return $ha_months[0];
}
// }}}
// {{{ firstDayOfYear()
/**
* Returns the Julian Day of the first day of the year (i.e. the no of
* days since 24th November, 4714 B.C.)
*
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return integer the number of days since 24th November, 4714 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function firstDayOfYear($pn_year)
{
return Date_Calc::firstDayOfMonth(Date_Calc::getFirstMonthOfYear($pn_year),
$pn_year);
}
// }}}
// {{{ lastDayOfYear()
/**
* Returns the Julian Day of the last day of the year (i.e. the no of
* days since 24th November, 4714 B.C.)
*
* @param int $pn_year the year (using 'Astronomical' year numbering)
*
* @return integer the number of days since 24th November, 4714 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function lastDayOfYear($pn_year)
{
return Date_Calc::firstDayOfYear($pn_year + 1) - 1;
}
// }}}
// {{{ dateToDaysJulian()
/**
* Converts a date in the proleptic Julian calendar to the no of days
* since 1st January, 4713 B.C.
*
* Returns the no of days since Monday, 1st January, 4713 B.C. in the
* proleptic Julian calendar (which is 1st January, -4712 using
* 'Astronomical' year numbering, and 24th November, 4713 B.C. in the
* proleptic Gregorian calendar). This is also the first day of the 'Julian
* Period' proposed by Joseph Scaliger in 1583, and the number of days
* since this date is known as the 'Julian Day'. (It is not directly
* to do with the Julian calendar, although this is where the name
* is derived from.)
*
* The algorithm is valid for all years (positive and negative), and
* also for years preceding 4713 B.C.
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year (using 'Astronomical' year numbering)
*
* @return int the number of days since 1st January, 4713 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function dateToDaysJulian($day, $month, $year)
{
if ($month > 2) {
// March = 0, April = 1, ..., December = 9,
// January = 10, February = 11
$month -= 3;
} else {
$month += 9;
--$year;
}
$hb_negativeyear = $year < 0;
if ($hb_negativeyear) {
// Subtract 1 because year 0 is a leap year;
// And N.B. that we must treat the leap years as occurring
// one year earlier than they do, because for the purposes
// of calculation, the year starts on 1st March:
//
return intval((1461 * $year + 1) / 4) +
intval((153 * $month + 2) / 5) +
$day + 1721116;
} else {
return intval(1461 * $year / 4) +
floor((153 * $month + 2) / 5) +
$day + 1721117;
}
}
// }}}
// {{{ daysToDateJulian()
/**
* Converts no of days since 1st January, 4713 B.C. (in the proleptic
* Julian calendar, which is year -4712 using 'Astronomical' year
* numbering) to Julian calendar date
*
* Returned date belongs to the proleptic Julian calendar, using
* 'Astronomical' year numbering.
*
* @param int $days the number of days since 1st January, 4713 B.C.
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function daysToDateJulian($days, $format = DATE_CALC_FORMAT)
{
$days = intval($days);
$days -= 1721117;
$days = floor(4 * $days - 1);
$day = floor($days / 4);
$year = floor((4 * $day + 3) / 1461);
$day = floor(4 * $day + 3 - 1461 * $year);
$day = floor(($day + 4) / 4);
$month = floor((5 * $day - 3) / 153);
$day = floor(5 * $day - 3 - 153 * $month);
$day = floor(($day + 5) / 5);
if ($month < 10) {
$month +=3;
} else {
$month -=9;
++$year;
}
return Date_Calc::dateFormat($day, $month, $year, $format);
}
// }}}
// {{{ isoWeekDate()
/**
* Returns array defining the 'ISO Week Date' as defined in ISO 8601
*
* Expects a date in the proleptic Gregorian calendar using 'Astronomical'
* year numbering, that is, with a year 0. Algorithm is valid for all
* years (positive and negative).
*
* N.B. the ISO week day no for Sunday is defined as 7, whereas this
* class and its related functions defines Sunday as 0.
*
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
*
* @return array array of ISO Year, ISO Week No, ISO Day No as
* integers
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function isoWeekDate($pn_day = 0, $pn_month = 0, $pn_year = null)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_jd = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year);
$hn_wd = Date_Calc::daysToDayOfWeek($hn_jd);
if ($hn_wd == 0)
$hn_wd = 7;
$hn_jd1 = Date_Calc::firstDayOfYear($pn_year);
$hn_day = $hn_jd - $hn_jd1 + 1;
if ($hn_wd <= $hn_jd - Date_Calc::lastDayOfYear($pn_year) + 3) {
// ISO week is the first week of the next ISO year:
//
$hn_year = $pn_year + 1;
$hn_isoweek = 1;
} else {
switch ($hn_wd1 = Date_Calc::daysToDayOfWeek($hn_jd1)) {
case 1:
case 2:
case 3:
case 4:
// Monday - Thursday:
//
$hn_year = $pn_year;
$hn_isoweek = floor(($hn_day + $hn_wd1 - 2) / 7) + 1;
break;
case 0:
$hn_wd1 = 7;
case 5:
case 6:
// Friday - Sunday:
//
if ($hn_day <= 8 - $hn_wd1) {
// ISO week is the last week of the previous ISO year:
//
list($hn_year, $hn_lastmonth, $hn_lastday) =
explode(" ",
Date_Calc::daysToDate($hn_jd1 - 1, "%Y %m %d"));
list($hn_year, $hn_isoweek, $hn_pisoday) =
Date_Calc::isoWeekDate($hn_lastday,
$hn_lastmonth,
$hn_year);
} else {
$hn_year = $pn_year;
$hn_isoweek = floor(($hn_day + $hn_wd1 - 9) / 7) + 1;
}
break;
}
}
return array((int) $hn_year, (int) $hn_isoweek, (int) $hn_wd);
}
// }}}
// {{{ gregorianToISO()
/**
* Converts from Gregorian Year-Month-Day to ISO Year-WeekNumber-WeekDay
*
* Uses ISO 8601 definitions.
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return string the date in ISO Year-WeekNumber-WeekDay format
* @access public
* @static
*/
function gregorianToISO($day, $month, $year)
{
list($yearnumber, $weeknumber, $weekday) =
Date_Calc::isoWeekDate($day, $month, $year);
return sprintf("%04d", $yearnumber) .
'-' .
sprintf("%02d", $weeknumber) .
'-' .
$weekday;
}
// }}}
// {{{ weekOfYear4th()
/**
* Returns week of the year counting week 1 as the week that contains 4th
* January
*
* Week 1 is determined to be the week that includes the 4th January, and
* therefore can be defined as the first week of the year that has at least
* 4 days. The previous week is counted as week 52 or 53 of the previous
* year. Note that this definition depends on which day is the first day of
* the week, and that if this is not passed as the '$pn_firstdayofweek'
* parameter, the default is assumed.
*
* Note also that the last day week of the year is likely to extend into
* the following year, except in the case that the last day of the week
* falls on 31st December.
*
* Also note that this is very similar to the ISO week returned by
* 'isoWeekDate()', the difference being that the ISO week always has
* 7 days, and if the 4th of January is a Friday, for example,
* ISO week 1 would start on Monday, 31st December in the previous year,
* whereas the week defined by this function would start on 1st January,
* but would be only 6 days long. Of course you can also set the day
* of the week, whereas the ISO week starts on a Monday by definition.
*
* Returned week is an integer from 1 to 53.
*
* @param int $pn_day the day of the month, default is current
* local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param int $pn_firstdayofweek optional integer specifying the first day
* of the week
*
* @return array array of year, week no as integers
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfYear4th($pn_day = 0,
$pn_month = 0,
$pn_year = null,
$pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year));
$hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year);
$hn_week = floor(($hn_day +
(10 + $hn_wd1 - $pn_firstdayofweek) % 7 +
3) / 7);
if ($hn_week > 0) {
$hn_year = $pn_year;
} else {
// Week number is the last week of the previous year:
//
list($hn_year, $hn_lastmonth, $hn_lastday) =
explode(" ",
Date_Calc::daysToDate(Date_Calc::lastDayOfYear($pn_year - 1),
"%Y %m %d"));
list($hn_year, $hn_week) =
Date_Calc::weekOfYear4th($hn_lastday,
$hn_lastmonth,
$hn_year,
$pn_firstdayofweek);
}
return array((int) $hn_year, (int) $hn_week);
}
// }}}
// {{{ weekOfYear7th()
/**
* Returns week of the year counting week 1 as the week that contains 7th
* January
*
* Week 1 is determined to be the week that includes the 7th January, and
* therefore can be defined as the first full week of the year. The
* previous week is counted as week 52 or 53 of the previous year. Note
* that this definition depends on which day is the first day of the week,
* and that if this is not passed as the '$pn_firstdayofweek' parameter, the
* default is assumed.
*
* Note also that the last day week of the year is likely to extend into
* the following year, except in the case that the last day of the week
* falls on 31st December.
*
* Returned week is an integer from 1 to 53.
*
* @param int $pn_day the day of the month, default is current
* local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param int $pn_firstdayofweek optional integer specifying the first day
* of the week
*
* @return array array of year, week no as integers
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfYear7th($pn_day = 0,
$pn_month = 0,
$pn_year = null,
$pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year));
$hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year);
$hn_week = floor(($hn_day + (6 + $hn_wd1 - $pn_firstdayofweek) % 7) / 7);
if ($hn_week > 0) {
$hn_year = $pn_year;
} else {
// Week number is the last week of the previous ISO year:
//
list($hn_year, $hn_lastmonth, $hn_lastday) = explode(" ", Date_Calc::daysToDate(Date_Calc::lastDayOfYear($pn_year - 1), "%Y %m %d"));
list($hn_year, $hn_week) = Date_Calc::weekOfYear7th($hn_lastday, $hn_lastmonth, $hn_year, $pn_firstdayofweek);
}
return array((int) $hn_year, (int) $hn_week);
}
// }}}
// {{{ dateSeason()
/**
* Determines julian date of the given season
*
* Adapted from previous work in Java by James Mark Hamilton.
*
* @param string $season the season to get the date for: VERNALEQUINOX,
* SUMMERSOLSTICE, AUTUMNALEQUINOX,
* or WINTERSOLSTICE
* @param string $year the year in four digit format. Must be between
* -1000 B.C. and 3000 A.D.
*
* @return float the julian date the season starts on
* @access public
* @static
*/
function dateSeason($season, $year = 0)
{
if ($year == '') {
$year = Date_Calc::dateNow('%Y');
}
if (($year >= -1000) && ($year <= 1000)) {
$y = $year / 1000.0;
switch ($season) {
case 'VERNALEQUINOX':
$juliandate = (((((((-0.00071 * $y) - 0.00111) * $y) + 0.06134) * $y) + 365242.1374) * $y) + 1721139.29189;
break;
case 'SUMMERSOLSTICE':
$juliandate = (((((((0.00025 * $y) + 0.00907) * $y) - 0.05323) * $y) + 365241.72562) * $y) + 1721233.25401;
break;
case 'AUTUMNALEQUINOX':
$juliandate = (((((((0.00074 * $y) - 0.00297) * $y) - 0.11677) * $y) + 365242.49558) * $y) + 1721325.70455;
break;
case 'WINTERSOLSTICE':
default:
$juliandate = (((((((-0.00006 * $y) - 0.00933) * $y) - 0.00769) * $y) + 365242.88257) * $y) + 1721414.39987;
}
} elseif (($year > 1000) && ($year <= 3000)) {
$y = ($year - 2000) / 1000;
switch ($season) {
case 'VERNALEQUINOX':
$juliandate = (((((((-0.00057 * $y) - 0.00411) * $y) + 0.05169) * $y) + 365242.37404) * $y) + 2451623.80984;
break;
case 'SUMMERSOLSTICE':
$juliandate = (((((((-0.0003 * $y) + 0.00888) * $y) + 0.00325) * $y) + 365241.62603) * $y) + 2451716.56767;
break;
case 'AUTUMNALEQUINOX':
$juliandate = (((((((0.00078 * $y) + 0.00337) * $y) - 0.11575) * $y) + 365242.01767) * $y) + 2451810.21715;
break;
case 'WINTERSOLSTICE':
default:
$juliandate = (((((((0.00032 * $y) - 0.00823) * $y) - 0.06223) * $y) + 365242.74049) * $y) + 2451900.05952;
}
}
return $juliandate;
}
// }}}
// {{{ dayOfYear()
/**
* Returns number of days since 31 December of year before given date
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
*
* @return int
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function dayOfYear($pn_day = 0, $pn_month = 0, $pn_year = null)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_jd = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year);
$hn_jd1 = Date_Calc::firstDayOfYear($pn_year);
return $hn_jd - $hn_jd1 + 1;
}
// }}}
// {{{ julianDate()
/**
* Returns number of days since 31 December of year before given date
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
*
* @return int
* @access public
* @static
* @deprecated Method deprecated in Release 1.5.0
*/
function julianDate($pn_day = 0, $pn_month = 0, $pn_year = null)
{
return Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year);
}
// }}}
// {{{ getWeekdayFullname()
/**
* Returns the full weekday name for the given date
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
*
* @return string the full name of the day of the week
* @access public
* @static
*/
function getWeekdayFullname($pn_day = 0, $pn_month = 0, $pn_year = null)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$weekday_names = Date_Calc::getWeekDays();
$weekday = Date_Calc::dayOfWeek($pn_day, $pn_month, $pn_year);
return $weekday_names[$weekday];
}
// }}}
// {{{ getWeekdayAbbrname()
/**
* Returns the abbreviated weekday name for the given date
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
* @param int $length the length of abbreviation
*
* @return string the abbreviated name of the day of the week
* @access public
* @static
* @see Date_Calc::getWeekdayFullname()
*/
function getWeekdayAbbrname($pn_day = 0,
$pn_month = 0,
$pn_year = null,
$length = 3)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$weekday_names = Date_Calc::getWeekDays(true);
$weekday = Date_Calc::dayOfWeek($pn_day, $pn_month, $pn_year);
return $weekday_names[$weekday];
}
// }}}
// {{{ getMonthFullname()
/**
* Returns the full month name for the given month
*
* @param int $month the month
*
* @return string the full name of the month
* @access public
* @static
*/
function getMonthFullname($month)
{
$month = (int)$month;
if (empty($month)) {
$month = (int)Date_Calc::dateNow('%m');
}
$month_names = Date_Calc::getMonthNames();
return $month_names[$month];
}
// }}}
// {{{ getMonthAbbrname()
/**
* Returns the abbreviated month name for the given month
*
* @param int $month the month
* @param int $length the length of abbreviation
*
* @return string the abbreviated name of the month
* @access public
* @static
* @see Date_Calc::getMonthFullname
*/
function getMonthAbbrname($month, $length = 3)
{
$month = (int)$month;
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
$month_names = Date_Calc::getMonthNames(true);
return $month_names[$month];
}
// }}}
// {{{ getMonthFromFullname()
/**
* Returns the numeric month from the month name or an abreviation
*
* Both August and Aug would return 8.
*
* @param string $month the name of the month to examine.
* Case insensitive.
*
* @return int the month's number
* @access public
* @static
*/
function getMonthFromFullName($month)
{
$month = strtolower($month);
$months = Date_Calc::getMonthNames();
while (list($id, $name) = each($months)) {
if (ereg($month, strtolower($name))) {
return $id;
}
}
return 0;
}
// }}}
// {{{ getWeekDays()
/**
* Returns an array of week day names
*
* Used to take advantage of the setlocale function to return language
* specific week days.
*
* @param int $pb_abbreviated whether to return the abbreviated form of the
* days
*
* @return array an array of week-day names
* @access public
* @static
*/
function getWeekDays($pb_abbreviated = false)
{
for ($i = 0; $i < 7; $i++) {
$weekdays[$i] = strftime($pb_abbreviated ? '%a' : '%A',
mktime(0, 0, 0, 1, $i, 2001));
}
return $weekdays;
}
// }}}
// {{{ daysToDayOfWeek()
/**
* Returns day of week for specified 'Julian Day'
*
* The algorithm is valid for all years (positive and negative), and
* also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
* and so the only limitation is platform-dependent (for 32-bit systems
* the maximum year would be something like about 1,465,190 A.D.).
*
* N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
*
* @param int $pn_days the number of days since 24th November, 4714 B.C.
*
* @return int integer from 0 to 7 where 0 represents Sunday
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function daysToDayOfWeek($pn_days)
{
// On Julian day 0 the day is Monday (PHP day 1):
//
$ret = ($pn_days + 1) % 7;
return $ret < 0 ? $ret + 7 : $ret;
}
// }}}
// {{{ dayOfWeek()
/**
* Returns day of week for given date (0 = Sunday)
*
* The algorithm is valid for all years (positive and negative).
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
*
* @return int the number of the day in the week
* @access public
* @static
*/
function dayOfWeek($day = null, $month = null, $year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
// if ($month <= 2) {
// $month += 12;
// --$year;
// }
// $wd = ($day +
// intval((13 * $month + 3) / 5) +
// $year +
// floor($year / 4) -
// floor($year / 100) +
// floor($year / 400) +
// 1) % 7;
// return (int) ($wd < 0 ? $wd + 7 : $wd);
return Date_Calc::daysToDayOfWeek(Date_Calc::dateToDays($day,
$month,
$year));
}
// }}}
// {{{ weekOfYearAbsolute()
/**
* Returns week of the year counting week 1 as 1st-7th January,
* regardless of what day 1st January falls on
*
* Returned value is an integer from 1 to 53. Week 53 will start on
* 31st December and have only one day, except in a leap year, in
* which it will start a day earlier and contain two days.
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
*
* @return int integer from 1 to 53
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfYearAbsolute($pn_day = 0, $pn_month = 0, $pn_year = null)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year);
return intval(($hn_day + 6) / 7);
}
// }}}
// {{{ weekOfYear1st()
/**
* Returns week of the year counting week 1 as the week that contains 1st
* January
*
* Week 1 is determined to be the week that includes the 1st January, even
* if this week extends into the previous year, in which case the week will
* only contain between 1 and 6 days of the current year. Note that this
* definition depends on which day is the first day of the week, and that if
* this is not passed as the '$pn_firstdayofweek' parameter, the default is
* assumed.
*
* Note also that the last day week of the year is also likely to contain
* less than seven days, except in the case that the last day of the week
* falls on 31st December.
*
* Returned value is an integer from 1 to 54. The year will only contain
* 54 weeks in the case of a leap year in which 1st January is the last day
* of the week, and 31st December is the first day of the week. In this
* case, both weeks 1 and 54 will contain one day only.
*
* @param int $pn_day the day of the month, default is current
* local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is
* current local year
* @param int $pn_firstdayofweek optional integer specifying the first day
* of the week
*
* @return int integer from 1 to 54
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfYear1st($pn_day = 0,
$pn_month = 0,
$pn_year = null,
$pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY)
{
if (is_null($pn_year)) {
$pn_year = Date_Calc::dateNow('%Y');
}
if (empty($pn_month)) {
$pn_month = Date_Calc::dateNow('%m');
}
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
$hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year));
$hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year);
return floor(($hn_day + (7 + $hn_wd1 - $pn_firstdayofweek) % 7 + 6) / 7);
}
// }}}
// {{{ weekOfYear()
/**
* Returns week of the year, where first Sunday is first day of first week
*
* N.B. this function is equivalent to calling:
*
* <code>Date_Calc::weekOfYear7th($day, $month, $year, 0)</code>
*
* Returned week is an integer from 1 to 53.
*
* @param int $pn_day the day of the month, default is current local day
* @param int $pn_month the month, default is current local month
* @param int $pn_year the year in four digit format, default is current
* local year
*
* @return int integer from 1 to 53
* @access public
* @static
* @see Date_Calc::weekOfYear7th
* @deprecated Method deprecated in Release 1.5.0
*/
function weekOfYear($pn_day = 0, $pn_month = 0, $pn_year = null)
{
$ha_week = Date_Calc::weekOfYear7th($pn_day, $pn_month, $pn_year, 0);
return $ha_week[1];
}
// }}}
// {{{ weekOfMonthAbsolute()
/**
* Returns week of the month counting week 1 as 1st-7th of the month,
* regardless of what day the 1st falls on
*
* Returned value is an integer from 1 to 5. Week 5 will start on
* the 29th of the month and have between 1 and 3 days, except
* in February in a non-leap year, when there will be 4 weeks only.
*
* @param int $pn_day the day of the month, default is current local day
*
* @return int integer from 1 to 5
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfMonthAbsolute($pn_day = 0)
{
if (empty($pn_day)) {
$pn_day = Date_Calc::dateNow('%d');
}
return intval(($pn_day + 6) / 7);
}
// }}}
// {{{ weekOfMonth()
/**
* Alias for 'weekOfMonthAbsolute()'
*
* @param int $pn_day the day of the month, default is current local day
*
* @return int integer from 1 to 5
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function weekOfMonth($pn_day = 0)
{
return Date_Calc::weekOfMonthAbsolute($pn_day);
}
// }}}
// {{{ quarterOfYear()
/**
* Returns quarter of the year for given date
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
*
* @return int the number of the quarter in the year
* @access public
* @static
*/
function quarterOfYear($day = 0, $month = 0, $year = null)
{
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return intval(($month - 1) / 3 + 1);
}
// }}}
// {{{ daysInMonth()
/**
* Returns the number of days in the given month
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
*
* @return int the number of days the month has
* @access public
* @static
*/
function daysInMonth($month = 0, $year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::lastDayOfMonth($month, $year) -
Date_Calc::firstDayOfMonth($month, $year) +
1;
}
// }}}
// {{{ daysInYear()
/**
* Returns the number of days in the given year
*
* @param int $year the year in four digit format, default is current local
* year
*
* @return int the number of days the year has
* @access public
* @static
*/
function daysInYear($year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
return Date_Calc::firstDayOfYear($year + 1) -
Date_Calc::firstDayOfYear($year);
}
// }}}
// {{{ weeksInMonth()
/**
* Returns the number of rows on a calendar month
*
* Useful for determining the number of rows when displaying a typical
* month calendar.
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
*
* @return int the number of weeks the month has
* @access public
* @static
*/
function weeksInMonth($month = 0, $year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
$FDOM = Date_Calc::firstOfMonthWeekday($month, $year);
if (DATE_CALC_BEGIN_WEEKDAY==1 && $FDOM==0) {
$first_week_days = 7 - $FDOM + DATE_CALC_BEGIN_WEEKDAY;
$weeks = 1;
} elseif (DATE_CALC_BEGIN_WEEKDAY==0 && $FDOM == 6) {
$first_week_days = 7 - $FDOM + DATE_CALC_BEGIN_WEEKDAY;
$weeks = 1;
} else {
$first_week_days = DATE_CALC_BEGIN_WEEKDAY - $FDOM;
$weeks = 0;
}
$first_week_days %= 7;
return ceil((Date_Calc::daysInMonth($month, $year)
- $first_week_days) / 7) + $weeks;
}
// }}}
// {{{ getCalendarWeek()
/**
* Return an array with days in week
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return array $week[$weekday]
* @access public
* @static
*/
function getCalendarWeek($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$week_array = array();
// date for the column of week
$curr_day = Date_Calc::beginOfWeek($day, $month, $year, '%E');
for ($counter = 0; $counter <= 6; $counter++) {
$week_array[$counter] = Date_Calc::daysToDate($curr_day, $format);
$curr_day++;
}
return $week_array;
}
// }}}
// {{{ getCalendarMonth()
/**
* Return a set of arrays to construct a calendar month for the given date
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return array $month[$row][$col]
* @access public
* @static
*/
function getCalendarMonth($month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
$month_array = array();
// date for the first row, first column of calendar month
if (DATE_CALC_BEGIN_WEEKDAY == 1) {
if (Date_Calc::firstOfMonthWeekday($month, $year) == 0) {
$curr_day = Date_Calc::firstDayOfMonth($month, $year) - 6;
} else {
$curr_day = Date_Calc::firstDayOfMonth($month, $year)
- Date_Calc::firstOfMonthWeekday($month, $year) + 1;
}
} else {
$curr_day = (Date_Calc::firstDayOfMonth($month, $year)
- Date_Calc::firstOfMonthWeekday($month, $year));
}
// number of days in this month
$daysInMonth = Date_Calc::daysInMonth($month, $year);
$weeksInMonth = Date_Calc::weeksInMonth($month, $year);
for ($row_counter = 0; $row_counter < $weeksInMonth; $row_counter++) {
for ($column_counter = 0; $column_counter <= 6; $column_counter++) {
$month_array[$row_counter][$column_counter] =
Date_Calc::daysToDate($curr_day, $format);
$curr_day++;
}
}
return $month_array;
}
// }}}
// {{{ getCalendarYear()
/**
* Return a set of arrays to construct a calendar year for the given date
*
* @param int $year the year in four digit format, default current
* local year
* @param string $format the string indicating how to format the output
*
* @return array $year[$month][$row][$col]
* @access public
* @static
*/
function getCalendarYear($year = null, $format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
$year_array = array();
for ($curr_month = 0; $curr_month <= 11; $curr_month++) {
$year_array[$curr_month] =
Date_Calc::getCalendarMonth($curr_month + 1,
$year, $format);
}
return $year_array;
}
// }}}
// {{{ prevDay()
/**
* Returns date of day before given date
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function prevDay($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
return Date_Calc::addDays(-1, $day, $month, $year, $format);
}
// }}}
// {{{ nextDay()
/**
* Returns date of day after given date
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function nextDay($day = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
return Date_Calc::addDays(1, $day, $month, $year, $format);
}
// }}}
// {{{ prevWeekday()
/**
* Returns date of the previous weekday, skipping from Monday to Friday
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function prevWeekday($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$days = Date_Calc::dateToDays($day, $month, $year);
if (Date_Calc::dayOfWeek($day, $month, $year) == 1) {
$days -= 3;
} elseif (Date_Calc::dayOfWeek($day, $month, $year) == 0) {
$days -= 2;
} else {
$days -= 1;
}
return Date_Calc::daysToDate($days, $format);
}
// }}}
// {{{ nextWeekday()
/**
* Returns date of the next weekday of given date, skipping from
* Friday to Monday
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function nextWeekday($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$days = Date_Calc::dateToDays($day, $month, $year);
if (Date_Calc::dayOfWeek($day, $month, $year) == 5) {
$days += 3;
} elseif (Date_Calc::dayOfWeek($day, $month, $year) == 6) {
$days += 2;
} else {
$days += 1;
}
return Date_Calc::daysToDate($days, $format);
}
// }}}
// {{{ daysToPrevDayOfWeek()
/**
* Returns 'Julian Day' of the previous specific day of the week
* from the given date.
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $days 'Julian Day', i.e. the no of days since 1st
* January, 4713 B.C.
* @param bool $onorbefore if true and days are same, returns current day
*
* @return int 'Julian Day', i.e. the no of days since 1st January,
* 4713 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function daysToPrevDayOfWeek($dow, $days, $onorbefore = false)
{
$curr_weekday = Date_Calc::daysToDayOfWeek($days);
if ($curr_weekday == $dow) {
if ($onorbefore) {
return $days;
} else {
return $days - 7;
}
} else if ($curr_weekday < $dow) {
return $days - 7 + $dow - $curr_weekday;
} else {
return $days - $curr_weekday + $dow;
}
}
// }}}
// {{{ prevDayOfWeek()
/**
* Returns date of the previous specific day of the week
* from the given date
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $day the day of the month, default is current local
* day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is
* current local year
* @param string $format the string indicating how to format the output
* @param bool $onorbefore if true and days are same, returns current day
*
* @return string the date in the desired format
* @access public
* @static
*/
function prevDayOfWeek($dow,
$day = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT,
$onorbefore = false)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$days = Date_Calc::dateToDays($day, $month, $year);
$days = Date_Calc::daysToPrevDayOfWeek($dow, $days, $onorbefore);
return Date_Calc::daysToDate($days, $format);
}
// }}}
// {{{ daysToNextDayOfWeek()
/**
* Returns 'Julian Day' of the next specific day of the week
* from the given date.
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $days 'Julian Day', i.e. the no of days since 1st
* January, 4713 B.C.
* @param bool $onorafter if true and days are same, returns current day
*
* @return int 'Julian Day', i.e. the no of days since 1st January,
* 4713 B.C.
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function daysToNextDayOfWeek($dow, $days, $onorafter = false)
{
$curr_weekday = Date_Calc::daysToDayOfWeek($days);
if ($curr_weekday == $dow) {
if ($onorafter) {
return $days;
} else {
return $days + 7;
}
} else if ($curr_weekday > $dow) {
return $days + 7 - $curr_weekday + $dow;
} else {
return $days + $dow - $curr_weekday;
}
}
// }}}
// {{{ nextDayOfWeek()
/**
* Returns date of the next specific day of the week
* from the given date
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $day the day of the month, default is current local
* day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is
* current local year
* @param string $format the string indicating how to format the output
* @param bool $onorafter if true and days are same, returns current day
*
* @return string the date in the desired format
* @access public
* @static
*/
function nextDayOfWeek($dow,
$day = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT,
$onorafter = false)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$days = Date_Calc::dateToDays($day, $month, $year);
$days = Date_Calc::daysToNextDayOfWeek($dow, $days, $onorafter);
return Date_Calc::daysToDate($days, $format);
}
// }}}
// {{{ prevDayOfWeekOnOrBefore()
/**
* Returns date of the previous specific day of the week
* on or before the given date
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function prevDayOfWeekOnOrBefore($dow,
$day = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
return Date_Calc::prevDayOfWeek($dow,
$day,
$month,
$year,
$format,
true);
}
// }}}
// {{{ nextDayOfWeekOnOrAfter()
/**
* Returns date of the next specific day of the week
* on or after the given date
*
* @param int $dow the day of the week (0 = Sunday)
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function nextDayOfWeekOnOrAfter($dow,
$day = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
return Date_Calc::nextDayOfWeek($dow,
$day,
$month,
$year,
$format,
true);
}
// }}}
// {{{ beginOfWeek()
/**
* Find the month day of the beginning of week for given date,
* using DATE_CALC_BEGIN_WEEKDAY
*
* Can return weekday of prev month.
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function beginOfWeek($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$hn_days = Date_Calc::dateToDays($day, $month, $year);
$this_weekday = Date_Calc::daysToDayOfWeek($hn_days);
$interval = (7 - DATE_CALC_BEGIN_WEEKDAY + $this_weekday) % 7;
return Date_Calc::daysToDate($hn_days - $interval, $format);
}
// }}}
// {{{ endOfWeek()
/**
* Find the month day of the end of week for given date,
* using DATE_CALC_BEGIN_WEEKDAY
*
* Can return weekday of following month.
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function endOfWeek($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
$hn_days = Date_Calc::dateToDays($day, $month, $year);
$this_weekday = Date_Calc::daysToDayOfWeek($hn_days);
$interval = (6 + DATE_CALC_BEGIN_WEEKDAY - $this_weekday) % 7;
return Date_Calc::daysToDate($hn_days + $interval, $format);
}
// }}}
// {{{ beginOfPrevWeek()
/**
* Find the month day of the beginning of week before given date,
* using DATE_CALC_BEGIN_WEEKDAY
*
* Can return weekday of prev month.
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function beginOfPrevWeek($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
list($hn_pwyear, $hn_pwmonth, $hn_pwday) =
explode(" ", Date_Calc::daysToDate(Date_Calc::dateToDays($day,
$month,
$year) - 7,
'%Y %m %d'));
return Date_Calc::beginOfWeek($hn_pwday,
$hn_pwmonth,
$hn_pwyear,
$format);
}
// }}}
// {{{ beginOfNextWeek()
/**
* Find the month day of the beginning of week after given date,
* using DATE_CALC_BEGIN_WEEKDAY
*
* Can return weekday of prev month.
*
* @param int $day the day of the month, default is current local day
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function beginOfNextWeek($day = 0, $month = 0, $year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
if (empty($day)) {
$day = Date_Calc::dateNow('%d');
}
list($hn_pwyear, $hn_pwmonth, $hn_pwday) =
explode(" ",
Date_Calc::daysToDate(Date_Calc::dateToDays($day,
$month,
$year) + 7,
'%Y %m %d'));
return Date_Calc::beginOfWeek($hn_pwday,
$hn_pwmonth,
$hn_pwyear,
$format);
}
// }}}
// {{{ beginOfMonth()
/**
* Return date of first day of month of given date
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::beginOfMonthBySpan()
* @deprecated Method deprecated in Release 1.4.4
*/
function beginOfMonth($month = 0, $year = null, $format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($month,
$year),
$month,
$year,
$format);
}
// }}}
// {{{ endOfMonth()
/**
* Return date of last day of month of given date
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::beginOfMonthBySpan()
* @since Method available since Release 1.5.0
* @deprecated Method deprecated in Release 1.5.0
*/
function endOfMonth($month = 0, $year = null, $format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::daysToDate(Date_Calc::lastDayOfMonth($month, $year),
$format);
}
// }}}
// {{{ beginOfPrevMonth()
/**
* Returns date of the first day of previous month of given date
*
* @param mixed $dummy irrelevant parameter
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::beginOfMonthBySpan()
* @deprecated Method deprecated in Release 1.4.4
*/
function beginOfPrevMonth($dummy = null,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
list($hn_pmyear, $hn_prevmonth) = Date_Calc::prevMonth($month, $year);
return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($hn_prevmonth,
$hn_pmyear),
$hn_prevmonth,
$hn_pmyear,
$format);
}
// }}}
// {{{ endOfPrevMonth()
/**
* Returns date of the last day of previous month for given date
*
* @param mixed $dummy irrelevant parameter
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::endOfMonthBySpan()
* @deprecated Method deprecated in Release 1.4.4
*/
function endOfPrevMonth($dummy = null,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::daysToDate(Date_Calc::firstDayOfMonth($month,
$year) - 1,
$format);
}
// }}}
// {{{ beginOfNextMonth()
/**
* Returns date of begin of next month of given date
*
* @param mixed $dummy irrelevant parameter
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::beginOfMonthBySpan()
* @deprecated Method deprecated in Release 1.4.4
*/
function beginOfNextMonth($dummy = null,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($month, $year);
return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($hn_nextmonth,
$hn_nmyear),
$hn_nextmonth,
$hn_nmyear,
$format);
}
// }}}
// {{{ endOfNextMonth()
/**
* Returns date of the last day of next month of given date
*
* @param mixed $dummy irrelevant parameter
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @see Date_Calc::endOfMonthBySpan()
* @deprecated Method deprecated in Release 1.4.4
*/
function endOfNextMonth($dummy = null,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($month, $year);
return Date_Calc::daysToDate(Date_Calc::lastDayOfMonth($hn_nextmonth,
$hn_nmyear),
$format);
}
// }}}
// {{{ beginOfMonthBySpan()
/**
* Returns date of the first day of the month in the number of months
* from the given date
*
* @param int $months the number of months from the date provided.
* Positive numbers go into the future.
* Negative numbers go into the past.
* 0 is the month presented in $month.
* @param string $month the month, default is current local month
* @param string $year the year in four digit format, default is the
* current local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.4.4
*/
function beginOfMonthBySpan($months = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::addMonths($months,
Date_Calc::getFirstDayOfMonth($month, $year),
$month,
$year,
$format);
}
// }}}
// {{{ endOfMonthBySpan()
/**
* Returns date of the last day of the month in the number of months
* from the given date
*
* @param int $months the number of months from the date provided.
* Positive numbers go into the future.
* Negative numbers go into the past.
* 0 is the month presented in $month.
* @param string $month the month, default is current local month
* @param string $year the year in four digit format, default is the
* current local year
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
* @since Method available since Release 1.4.4
*/
function endOfMonthBySpan($months = 0,
$month = 0,
$year = null,
$format = DATE_CALC_FORMAT)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
$hn_days = Date_Calc::addMonthsToDays($months + 1,
Date_Calc::firstDayOfMonth($month, $year)) - 1;
return Date_Calc::daysToDate($hn_days, $format);
}
// }}}
// {{{ firstOfMonthWeekday()
/**
* Find the day of the week for the first of the month of given date
*
* @param int $month the month, default is current local month
* @param int $year the year in four digit format, default is current
* local year
*
* @return int number of weekday for the first day, 0=Sunday
* @access public
* @static
*/
function firstOfMonthWeekday($month = 0, $year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if (empty($month)) {
$month = Date_Calc::dateNow('%m');
}
return Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfMonth($month,
$year));
}
// }}}
// {{{ nWeekdayOfMonth()
/**
* Calculates the date of the Nth weekday of the month,
* such as the second Saturday of January 2000
*
* @param int $week the number of the week to get
* (1 = first, etc. Also can be 'last'.)
* @param int $dow the day of the week (0 = Sunday)
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
* @param string $format the string indicating how to format the output
*
* @return string the date in the desired format
* @access public
* @static
*/
function nWeekdayOfMonth($week, $dow, $month, $year,
$format = DATE_CALC_FORMAT)
{
if (is_numeric($week)) {
$DOW1day = ($week - 1) * 7 + 1;
$DOW1 = Date_Calc::dayOfWeek($DOW1day, $month, $year);
$wdate = ($week - 1) * 7 + 1 + (7 + $dow - $DOW1) % 7;
if ($wdate > Date_Calc::daysInMonth($month, $year)) {
return -1;
} else {
return Date_Calc::dateFormat($wdate, $month, $year, $format);
}
} elseif ($week == 'last' && $dow < 7) {
$lastday = Date_Calc::daysInMonth($month, $year);
$lastdow = Date_Calc::dayOfWeek($lastday, $month, $year);
$diff = $dow - $lastdow;
if ($diff > 0) {
return Date_Calc::dateFormat($lastday - (7 - $diff), $month,
$year, $format);
} else {
return Date_Calc::dateFormat($lastday + $diff, $month,
$year, $format);
}
} else {
return -1;
}
}
// }}}
// {{{ isValidDate()
/**
* Returns true for valid date, false for invalid date
*
* Uses the proleptic Gregorian calendar, with the year 0 (1 B.C.)
* assumed to be valid and also assumed to be a leap year.
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return bool
* @access public
* @static
*/
function isValidDate($day, $month, $year)
{
if ($day < 1 || $month < 1 || $month > 12)
return false;
if ($month == 2) {
if (Date_Calc::isLeapYearGregorian($year)) {
return $day <= 29;
} else {
return $day <= 28;
}
} elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
return $day <= 30;
} else {
return $day <= 31;
}
}
// }}}
// {{{ isLeapYearGregorian()
/**
* Returns true for a leap year, else false
*
* Uses the proleptic Gregorian calendar. The year 0 (1 B.C.) is
* assumed in this algorithm to be a leap year. The function is
* valid for all years, positive and negative.
*
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return bool
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function isLeapYearGregorian($year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
return (($year % 4 == 0) &&
($year % 100 != 0)) ||
($year % 400 == 0);
}
// }}}
// {{{ isLeapYearJulian()
/**
* Returns true for a leap year, else false
*
* Uses the proleptic Julian calendar. The year 0 (1 B.C.) is
* assumed in this algorithm to be a leap year. The function is
* valid for all years, positive and negative.
*
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return boolean
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function isLeapYearJulian($year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
return $year % 4 == 0;
}
// }}}
// {{{ isLeapYear()
/**
* Returns true for a leap year, else false
*
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return boolean
* @access public
* @static
*/
function isLeapYear($year = null)
{
if (is_null($year)) {
$year = Date_Calc::dateNow('%Y');
}
if ($year < 1582) {
// pre Gregorio XIII - 1582
return Date_Calc::isLeapYearJulian($year);
} else {
// post Gregorio XIII - 1582
return Date_Calc::isLeapYearGregorian($year);
}
}
// }}}
// {{{ isFutureDate()
/**
* Determines if given date is a future date from now
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return bool
* @access public
* @static
*/
function isFutureDate($day, $month, $year)
{
$this_year = Date_Calc::dateNow('%Y');
$this_month = Date_Calc::dateNow('%m');
$this_day = Date_Calc::dateNow('%d');
if ($year > $this_year) {
return true;
} elseif ($year == $this_year) {
if ($month > $this_month) {
return true;
} elseif ($month == $this_month) {
if ($day > $this_day) {
return true;
}
}
}
return false;
}
// }}}
// {{{ isPastDate()
/**
* Determines if given date is a past date from now
*
* @param int $day the day of the month
* @param int $month the month
* @param int $year the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return boolean
* @access public
* @static
*/
function isPastDate($day, $month, $year)
{
$this_year = Date_Calc::dateNow('%Y');
$this_month = Date_Calc::dateNow('%m');
$this_day = Date_Calc::dateNow('%d');
if ($year < $this_year) {
return true;
} elseif ($year == $this_year) {
if ($month < $this_month) {
return true;
} elseif ($month == $this_month) {
if ($day < $this_day) {
return true;
}
}
}
return false;
}
// }}}
// {{{ dateDiff()
/**
* Returns number of days between two given dates
*
* @param int $day1 the day of the month
* @param int $month1 the month
* @param int $year1 the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
* @param int $day2 the day of the month
* @param int $month2 the month
* @param int $year2 the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return int the absolute number of days between the two dates.
* If an error occurs, -1 is returned.
* @access public
* @static
*/
function dateDiff($day1, $month1, $year1, $day2, $month2, $year2)
{
if (!Date_Calc::isValidDate($day1, $month1, $year1)) {
return -1;
}
if (!Date_Calc::isValidDate($day2, $month2, $year2)) {
return -1;
}
return abs(Date_Calc::dateToDays($day1, $month1, $year1)
- Date_Calc::dateToDays($day2, $month2, $year2));
}
// }}}
// {{{ compareDates()
/**
* Compares two dates
*
* @param int $day1 the day of the month
* @param int $month1 the month
* @param int $year1 the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
* @param int $day2 the day of the month
* @param int $month2 the month
* @param int $year2 the year. Use the complete year instead of the
* abbreviated version. E.g. use 2005, not 05.
*
* @return int 0 if the dates are equal. 1 if date 1 is later, -1
* if date 1 is earlier.
* @access public
* @static
*/
function compareDates($day1, $month1, $year1, $day2, $month2, $year2)
{
$ndays1 = Date_Calc::dateToDays($day1, $month1, $year1);
$ndays2 = Date_Calc::dateToDays($day2, $month2, $year2);
if ($ndays1 == $ndays2) {
return 0;
}
return ($ndays1 > $ndays2) ? 1 : -1;
}
// }}}
// {{{ round()
/**
* Rounds the date according to the specified precision
*
* The precision parameter must be one of the following constants:
*
* <code>DATE_PRECISION_YEAR</code>
* <code>DATE_PRECISION_MONTH</code>
* <code>DATE_PRECISION_DAY</code>
* <code>DATE_PRECISION_HOUR</code>
* <code>DATE_PRECISION_10MINUTES</code>
* <code>DATE_PRECISION_MINUTE</code>
* <code>DATE_PRECISION_10SECONDS</code>
* <code>DATE_PRECISION_SECOND</code>
*
* The precision can also be specified as an integral offset from
* one of these constants, where the offset reflects a precision
* of 10 to the power of the offset greater than the constant.
* For example:
*
* <code>DATE_PRECISION_YEAR - 1</code> rounds the date to the nearest 10
* years
* <code>DATE_PRECISION_YEAR - 3</code> rounds the date to the nearest 1000
* years
* <code>DATE_PRECISION_SECOND + 1</code> rounds the date to 1 decimal
* point of a second
* <code>DATE_PRECISION_SECOND + 1</code> rounds the date to 3 decimal
* points of a second
* <code>DATE_PRECISION_SECOND + 1</code> rounds the date to the nearest 10
* seconds (thus it is equivalent to
* DATE_PRECISION_10SECONDS)
*
* N.B. This function requires a time in UTC if both the precision is at
* least DATE_PRECISION_SECOND and leap seconds are being counted, otherwise
* any local time is acceptable.
*
* @param int $pn_precision a 'DATE_PRECISION_*' constant
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param mixed $pn_second the second as integer or float
* @param bool $pb_countleap whether to count leap seconds (defaults to
* DATE_COUNT_LEAP_SECONDS)
*
* @return array array of year, month, day, hour, minute, second
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function round($pn_precision,
$pn_day,
$pn_month,
$pn_year,
$pn_hour = 0,
$pn_minute = 0,
$pn_second = 0,
$pb_countleap = DATE_COUNT_LEAP_SECONDS)
{
if ($pn_precision <= DATE_PRECISION_YEAR) {
$hn_month = 0;
$hn_day = 0;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
if ($pn_precision < DATE_PRECISION_YEAR) {
$hn_year = round($pn_year, $pn_precision - DATE_PRECISION_YEAR);
} else {
// Check part-year:
//
$hn_midyear = (Date_Calc::firstDayOfYear($pn_year + 1) -
Date_Calc::firstDayOfYear($pn_year)) / 2;
if (($hn_days = Date_Calc::dayOfYear($pn_day,
$pn_month,
$pn_year)) <=
$hn_midyear - 1) {
$hn_year = $pn_year;
} else if ($hn_days >= $hn_midyear) {
// Round up:
//
$hn_year = $pn_year + 1;
} else {
// Take time into account:
//
$hn_partday = Date_Calc::secondsPastMidnight($pn_hour,
$pn_minute,
$pn_second) /
86400;
if ($hn_partday >= $hn_midyear - $hn_days) {
// Round up:
//
$hn_year = $pn_year + 1;
} else {
$hn_year = $pn_year;
}
}
}
} else if ($pn_precision == DATE_PRECISION_MONTH) {
$hn_year = $pn_year;
$hn_day = 0;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
$hn_firstofmonth = Date_Calc::firstDayOfMonth($pn_month, $pn_year);
$hn_midmonth = (Date_Calc::lastDayOfMonth($pn_month, $pn_year) +
1 -
$hn_firstofmonth) / 2;
if (($hn_days = Date_Calc::dateToDays($pn_day,
$pn_month,
$pn_year) -
$hn_firstofmonth) <= $hn_midmonth - 1) {
$hn_month = $pn_month;
} else if ($hn_days >= $hn_midmonth) {
// Round up:
//
list($hn_year, $hn_month) = Date_Calc::nextMonth($pn_month,
$pn_year);
} else {
// Take time into account:
//
$hn_partday = Date_Calc::secondsPastMidnight($pn_hour,
$pn_minute,
$pn_second) /
86400;
if ($hn_partday >= $hn_midmonth - $hn_days) {
// Round up:
//
list($hn_year, $hn_month) = Date_Calc::nextMonth($pn_month,
$pn_year);
} else {
$hn_month = $pn_month;
}
}
} else if ($pn_precision == DATE_PRECISION_DAY) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
if (Date_Calc::secondsPastMidnight($pn_hour,
$pn_minute,
$pn_second) >= 43200) {
// Round up:
//
list($hn_year, $hn_month, $hn_day) =
explode(" ", Date_Calc::nextDay($pn_day,
$pn_month,
$pn_year,
"%Y %m %d"));
} else {
$hn_day = $pn_day;
}
} else if ($pn_precision == DATE_PRECISION_HOUR) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
$hn_minute = 0;
$hn_second = 0;
if (Date_Calc::secondsPastTheHour($pn_minute, $pn_second) >= 1800) {
// Round up:
//
list($hn_year, $hn_month, $hn_day, $hn_hour) =
Date_Calc::addHours(1,
$pn_day,
$pn_month,
$pn_year,
$pn_hour);
} else {
$hn_hour = $pn_hour;
}
} else if ($pn_precision <= DATE_PRECISION_MINUTE) {
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
$hn_hour = $pn_hour;
$hn_second = 0;
if ($pn_precision < DATE_PRECISION_MINUTE) {
$hn_minute = round($pn_minute,
$pn_precision - DATE_PRECISION_MINUTE);
} else {
// Check seconds:
//
if ($pn_second >= 30) {
// Round up:
//
list($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute) =
Date_Calc::addMinutes(1,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute);
} else {
$hn_minute = $pn_minute;
}
}
} else {
// Precision is at least (DATE_PRECISION_SECOND - 1):
//
$hn_year = $pn_year;
$hn_month = $pn_month;
$hn_day = $pn_day;
$hn_hour = $pn_hour;
$hn_minute = $pn_minute;
$hn_second = round($pn_second,
$pn_precision - DATE_PRECISION_SECOND);
if (fmod($hn_second, 1) == 0.0) {
$hn_second = (int) $hn_second;
if ($hn_second != intval($pn_second)) {
list($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute,
$hn_second) =
Date_Calc::addSeconds($hn_second - intval($pn_second),
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute,
intval($pn_second),
$pn_precision >=
DATE_PRECISION_SECOND &&
$pb_countleap);
//
// (N.B. if rounded to nearest 10 seconds,
// user does not expect seconds to be '60')
}
}
}
return array((int) $hn_year,
(int) $hn_month,
(int) $hn_day,
(int) $hn_hour,
(int) $hn_minute,
$hn_second);
}
// }}}
// {{{ roundSeconds()
/**
* Rounds seconds up or down to the nearest specified unit
*
* @param int $pn_precision number of digits after the decimal point
* @param int $pn_day the day of the month
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param mixed $pn_second the second as integer or float
* @param bool $pb_countleap whether to count leap seconds (defaults to
* DATE_COUNT_LEAP_SECONDS)
*
* @return array array of year, month, day, hour, minute, second
* @access public
* @static
* @since Method available since Release 1.5.0
*/
function roundSeconds($pn_precision,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute,
$pn_second,
$pb_countleap = DATE_COUNT_LEAP_SECONDS)
{
return Date_Calc::round(DATE_PRECISION_SECOND + $pn_precision,
$pn_day,
$pn_month,
$pn_year,
$pn_hour,
$pn_minute,
$pn_second);
}
// }}}
}
// }}}
/*
* Local variables:
* mode: php
* tab-width: 4
* c-basic-offset: 4
* c-hanging-comment-ender-p: nil
* End:
*/
/**
* This file contains the Horde_Date_Recurrence class and according constants.
*
* $Horde: framework/Date/Date/Recurrence.php,v 1.7.2.16 2010-10-14 14:18:05 jan Exp $
*
* Copyright 2007-2009 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @since Horde 3.2
* @package Horde_Date
*/
/** Horde_Date */
// require_once 'Horde/Date.php';
/** Date_Calc */
//require_once 'Date/Calc.php';
/** No recurrence. */
define('HORDE_DATE_RECUR_NONE', 0);
/** Recurs daily. */
define('HORDE_DATE_RECUR_DAILY', 1);
/** Recurs weekly. */
define('HORDE_DATE_RECUR_WEEKLY', 2);
/** Recurs monthly on the same date. */
define('HORDE_DATE_RECUR_MONTHLY_DATE', 3);
/** Recurs monthly on the same week day. */
define('HORDE_DATE_RECUR_MONTHLY_WEEKDAY', 4);
/** Recurs yearly on the same date. */
define('HORDE_DATE_RECUR_YEARLY_DATE', 5);
/** Recurs yearly on the same day of the year. */
define('HORDE_DATE_RECUR_YEARLY_DAY', 6);
/** Recurs yearly on the same week day. */
define('HORDE_DATE_RECUR_YEARLY_WEEKDAY', 7);
/**
* The Horde_Date_Recurrence class implements algorithms for calculating
* recurrences of events, including several recurrence types, intervals,
* exceptions, and conversion from and to vCalendar and iCalendar recurrence
* rules.
*
* All methods expecting dates as parameters accept all values that the
* Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date
* object, an ISO time string or a hash.
*
* @author Jan Schneider <jan@horde.org>
* @since Horde 3.2
* @package Horde_Date
*/
class Horde_Date_Recurrence {
/**
* The start time of the event.
*
* @var Horde_Date
*/
var $start;
/**
* The end date of the recurrence interval.
*
* @var Horde_Date
*/
var $recurEnd = null;
/**
* The number of recurrences.
*
* @var integer
*/
var $recurCount = null;
/**
* The type of recurrence this event follows. HORDE_DATE_RECUR_* constant.
*
* @var integer
*/
var $recurType = HORDE_DATE_RECUR_NONE;
/**
* The length of time between recurrences. The time unit depends on the
* recurrence type.
*
* @var integer
*/
var $recurInterval = 1;
/**
* Any additional recurrence data.
*
* @var integer
*/
var $recurData = null;
/**
* BYDAY recurrence number
*
* @var integer
*/
var $recurNthDay = 0;
/**
* BYMONTH recurrence data
*
* @var array
*/
var $recurMonths = array();
/**
* All the exceptions from recurrence for this event.
*
* @var array
*/
var $exceptions = array();
/**
* All the dates this recurrence has been marked as completed.
*
* @var array
*/
var $completions = array();
/**
* Constructor.
*
* @param Horde_Date $start Start of the recurring event.
*/
function Horde_Date_Recurrence($start)
{
$this->start = new Horde_Date($start);
}
/**
* Checks if this event recurs on a given day of the week.
*
* @param integer $dayMask A mask consisting of HORDE_DATE_MASK_*
* constants specifying the day(s) to check.
*
* @return boolean True if this event recurs on the given day(s).
*/
function recurOnDay($dayMask)
{
return ($this->recurData & $dayMask);
}
/**
* Specifies the days this event recurs on.
*
* @param integer $dayMask A mask consisting of HORDE_DATE_MASK_*
* constants specifying the day(s) to recur on.
*/
function setRecurOnDay($dayMask)
{
$this->recurData = $dayMask;
}
/**
* Returns the days this event recurs on.
*
* @return integer A mask consisting of HORDE_DATE_MASK_* constants
* specifying the day(s) this event recurs on.
*/
function getRecurOnDays()
{
return $this->recurData;
}
/**
* Specifies the months for yearly (weekday) recurrence
*
* @param array $months List of months (integers) this event recurs on.
*/
function setRecurByMonth($months)
{
$this->recurMonths = (array)$months;
}
/**
* Returns a list of months this yearly event recurs on
*
* @return array List of months (integers) this event recurs on.
*/
function getRecurByMonth()
{
return $this->recurMonths;
}
/**
*
* @param integer $nthDay The nth weekday of month to repeat events on
*/
function setRecurNthWeekday($nthDay)
{
$this->recurNthDay = (int)$nthDay;
}
/**
*
* @return integer The nth weekday of month to repeat events.
*/
function getRecurNthWeekday()
{
return $this->recurNthDay;
}
/**
* Returns whether this event has a specific recurrence type.
*
* @param integer $recurrence HORDE_DATE_RECUR_* constant of the
* recurrence type to check for.
*
* @return boolean True if the event has the specified recurrence type.
*/
function hasRecurType($recurrence)
{
return ($recurrence == $this->recurType);
}
/**
* Sets a recurrence type for this event.
*
* @param integer $recurrence A HORDE_DATE_RECUR_* constant.
*/
function setRecurType($recurrence)
{
$this->recurType = $recurrence;
}
/**
* Returns recurrence type of this event.
*
* @return integer A HORDE_DATE_RECUR_* constant.
*/
function getRecurType()
{
return $this->recurType;
}
/**
* Returns a description of this event's recurring type.
*
* @return string Human readable recurring type.
*/
function getRecurName()
{
switch ($this->getRecurType()) {
case HORDE_DATE_RECUR_NONE: return _("No recurrence");
case HORDE_DATE_RECUR_DAILY: return _("Daily");
case HORDE_DATE_RECUR_WEEKLY: return _("Weekly");
case HORDE_DATE_RECUR_MONTHLY_DATE:
case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: return _("Monthly");
case HORDE_DATE_RECUR_YEARLY_DATE:
case HORDE_DATE_RECUR_YEARLY_DAY:
case HORDE_DATE_RECUR_YEARLY_WEEKDAY: return _("Yearly");
}
}
/**
* Sets the length of time between recurrences of this event.
*
* @param integer $interval The time between recurrences.
*/
function setRecurInterval($interval)
{
if ($interval > 0) {
$this->recurInterval = $interval;
}
}
/**
* Retrieves the length of time between recurrences of this event.
*
* @return integer The number of seconds between recurrences.
*/
function getRecurInterval()
{
return $this->recurInterval;
}
/**
* Sets the number of recurrences of this event.
*
* @param integer $count The number of recurrences.
*/
function setRecurCount($count)
{
if ($count > 0) {
$this->recurCount = (int)$count;
// Recurrence counts and end dates are mutually exclusive.
$this->recurEnd = null;
} else {
$this->recurCount = null;
}
}
/**
* Retrieves the number of recurrences of this event.
*
* @return integer The number recurrences.
*/
function getRecurCount()
{
return $this->recurCount;
}
/**
* Returns whether this event has a recurrence with a fixed count.
*
* @return boolean True if this recurrence has a fixed count.
*/
function hasRecurCount()
{
return isset($this->recurCount);
}
/**
* Sets the start date of the recurrence interval.
*
* @param Horde_Date $start The recurrence start.
*/
function setRecurStart($start)
{
$this->start = new Horde_Date($start);
}
/**
* Retrieves the start date of the recurrence interval.
*
* @return Horde_Date The recurrence start.
*/
function getRecurStart()
{
return $this->start;
}
/**
* Sets the end date of the recurrence interval.
*
* @param Horde_Date $end The recurrence end.
*/
function setRecurEnd($end)
{
if (!empty($end)) {
// Recurrence counts and end dates are mutually exclusive.
$this->recurCount = null;
}
$this->recurEnd = new Horde_Date($end);
}
/**
* Retrieves the end date of the recurrence interval.
*
* @return Horde_Date The recurrence end.
*/
function getRecurEnd()
{
return $this->recurEnd;
}
/**
* Returns whether this event has a recurrence end.
*
* @return boolean True if this recurrence ends.
*/
function hasRecurEnd()
{
return isset($this->recurEnd) && isset($this->recurEnd->year) &&
$this->recurEnd->year != 9999;
}
/**
* Finds the next recurrence of this event that's after $afterDate.
*
* @param Horde_Date $afterDate Return events after this date.
*
* @return Horde_Date|boolean The date of the next recurrence or false
* if the event does not recur after
* $afterDate.
*/
function nextRecurrence($afterDate)
{
$after = new Horde_Date($afterDate);
$after->correct();
if ($this->start->compareDateTime($after) >= 0) {
return new Horde_Date($this->start);
}
if ($this->recurInterval == 0) {
return false;
}
switch ($this->getRecurType()) {
case HORDE_DATE_RECUR_DAILY:
$diff = Date_Calc::dateDiff($this->start->mday, $this->start->month, $this->start->year, $after->mday, $after->month, $after->year);
$recur = ceil($diff / $this->recurInterval);
if ($this->recurCount && $recur >= $this->recurCount) {
return false;
}
$recur *= $this->recurInterval;
$next = new Horde_Date($this->start);
list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y'));
if ((!$this->hasRecurEnd() ||
$next->compareDateTime($this->recurEnd) <= 0) &&
$next->compareDateTime($after) >= 0) {
return new Horde_Date($next);
}
break;
case HORDE_DATE_RECUR_WEEKLY:
if (empty($this->recurData)) {
return false;
}
list($start_week->mday, $start_week->month, $start_week->year) = explode('/', Date_Calc::beginOfWeek($this->start->mday, $this->start->month, $this->start->year, '%e/%m/%Y'));
$start_week->hour = $this->start->hour;
$start_week->min = $this->start->min;
$start_week->sec = $this->start->sec;
list($after_week->mday, $after_week->month, $after_week->year) = explode('/', Date_Calc::beginOfWeek($after->mday, $after->month, $after->year, '%e/%m/%Y'));
$after_week_end = new Horde_Date($after_week);
$after_week_end->mday += 7;
$after_week_end->correct();
$diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year,
$after_week->mday, $after_week->month, $after_week->year);
$interval = $this->recurInterval * 7;
$repeats = floor($diff / $interval);
if ($diff % $interval < 7) {
$recur = $diff;
} else {
/**
* If the after_week is not in the first week interval the
* search needs to skip ahead a complete interval. The way it is
* calculated here means that an event that occurs every second
* week on Monday and Wednesday with the event actually starting
* on Tuesday or Wednesday will only have one incidence in the
* first week.
*/
$recur = $interval * ($repeats + 1);
}
if ($this->hasRecurCount()) {
$recurrences = 0;
/**
* Correct the number of recurrences by the number of events
* that lay between the start of the start week and the
* recurrence start.
*/
$next = new Horde_Date($start_week);
while ($next->compareDateTime($this->start) < 0) {
if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
$recurrences--;
}
++$next->mday;
$next->correct();
}
if ($repeats > 0) {
$weekdays = $this->recurData;
$total_recurrences_per_week = 0;
while ($weekdays > 0) {
if ($weekdays % 2) {
$total_recurrences_per_week++;
}
$weekdays = ($weekdays - ($weekdays % 2)) / 2;
}
$recurrences += $total_recurrences_per_week * $repeats;
}
}
$next = $start_week;
list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y'));
$next = new Horde_Date($next);
while ($next->compareDateTime($after) < 0 &&
$next->compareDateTime($after_week_end) < 0) {
if ($this->hasRecurCount()
&& $next->compareDateTime($after) < 0
&& $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
$recurrences++;
}
++$next->mday;
$next->correct();
}
if ($this->hasRecurCount() &&
$recurrences >= $this->recurCount) {
return false;
}
if (!$this->hasRecurEnd() ||
$next->compareDateTime($this->recurEnd) <= 0) {
if ($next->compareDateTime($after_week_end) >= 0) {
return $this->nextRecurrence($after_week_end);
}
while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) &&
$next->compareDateTime($after_week_end) < 0) {
++$next->mday;
$next->correct();
}
if (!$this->hasRecurEnd() ||
$next->compareDateTime($this->recurEnd) <= 0) {
if ($next->compareDateTime($after_week_end) >= 0) {
return $this->nextRecurrence($after_week_end);
} else {
return $next;
}
}
}
break;
case HORDE_DATE_RECUR_MONTHLY_DATE:
$start = new Horde_Date($this->start);
if ($after->compareDateTime($start) < 0) {
$after = $start;
}
// If we're starting past this month's recurrence of the event,
// look in the next month on the day the event recurs.
if ($after->mday > $start->mday) {
++$after->month;
$after->mday = $start->mday;
$after->correct();
}
// Adjust $start to be the first match.
$offset = ($after->month - $start->month) + ($after->year - $start->year) * 12;
$offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
if ($this->recurCount &&
($offset / $this->recurInterval) >= $this->recurCount) {
return false;
}
$start->month += $offset;
$count = $offset / $this->recurInterval;
do {
if ($this->recurCount &&
$count++ >= $this->recurCount) {
return false;
}
// Don't correct for day overflow; we just skip February 30th,
// for example.
$start->correct(HORDE_DATE_MASK_MONTH);
// Bail if we've gone past the end of recurrence.
if ($this->hasRecurEnd() &&
$this->recurEnd->compareDateTime($start) < 0) {
return false;
}
if ($start->isValid()) {
return $start;
}
// If the interval is 12, and the date isn't valid, then we
// need to see if February 29th is an option. If not, then the
// event will _never_ recur, and we need to stop checking to
// avoid an infinite loop.
if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) {
return false;
}
// Add the recurrence interval.
$start->month += $this->recurInterval;
} while (true);
break;
case HORDE_DATE_RECUR_MONTHLY_WEEKDAY:
// Start with the start date of the event.
$estart = new Horde_Date($this->start);
// What day of the week, and week of the month, do we recur on?
if ($this->recurNthDay != 0) {
$nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay;
$weekday = log($this->recurData, 2);
} else {
$nth = ceil($this->start->mday / 7);
$weekday = $estart->dayOfWeek();
}
// Adjust $estart to be the first candidate.
$offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12;
$offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
// Adjust our working date until it's after $after.
$estart->month += $offset - $this->recurInterval;
$count = $offset / $this->recurInterval;
do {
if ($this->recurCount &&
$count++ >= $this->recurCount) {
return false;
}
$estart->month += $this->recurInterval;
$estart->correct();
$next = new Horde_Date($estart);
if ($this->recurNthDay) {
list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y'));
} else {
$next->setNthWeekday($weekday, $nth);
}
if ($next->compareDateTime($after) < 0) {
// We haven't made it past $after yet, try again.
continue;
}
if ($this->hasRecurEnd() &&
$next->compareDateTime($this->recurEnd) > 0) {
// We've gone past the end of recurrence; we can give up
// now.
return false;
}
// We have a candidate to return.
break;
} while (true);
return $next;
case HORDE_DATE_RECUR_YEARLY_DATE:
// Start with the start date of the event.
$estart = new Horde_Date($this->start);
if ($after->month > $estart->month ||
($after->month == $estart->month && $after->mday > $estart->mday)) {
++$after->year;
$after->month = $estart->month;
$after->mday = $estart->mday;
}
// Seperate case here for February 29th
if ($estart->month == 2 && $estart->mday == 29) {
while (!Horde_Date::isLeapYear($after->year)) {
++$after->year;
}
}
// Adjust $estart to be the first candidate.
$offset = $after->year - $estart->year;
if ($offset > 0) {
$offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
$estart->year += $offset;
}
// We've gone past the end of recurrence; give up.
if ($this->recurCount &&
$offset >= $this->recurCount) {
return false;
}
if ($this->hasRecurEnd() &&
$this->recurEnd->compareDateTime($estart) < 0) {
return false;
}
return $estart;
case HORDE_DATE_RECUR_YEARLY_DAY:
// Check count first.
$dayofyear = $this->start->dayOfYear();
$count = ($after->year - $this->start->year) / $this->recurInterval + 1;
if ($this->recurCount &&
($count > $this->recurCount ||
($count == $this->recurCount &&
$after->dayOfYear() > $dayofyear))) {
return false;
}
// Start with a rough interval.
$estart = new Horde_Date($this->start);
$estart->year += floor($count - 1) * $this->recurInterval;
// Now add the difference to the required day of year.
$estart->mday += $dayofyear - $estart->dayOfYear();
$estart->correct();
// Add an interval if the estimation was wrong.
if ($estart->compareDate($after) < 0) {
$estart->year += $this->recurInterval;
$estart->mday += $dayofyear - $estart->dayOfYear();
$estart->correct();
}
// We've gone past the end of recurrence; give up.
if ($this->hasRecurEnd() &&
$this->recurEnd->compareDateTime($estart) < 0) {
return false;
}
return $estart;
case HORDE_DATE_RECUR_YEARLY_WEEKDAY:
// Start with the start date of the event.
$estart = new Horde_Date($this->start);
// What day of the week, and week of the month, do we recur on?
if ($this->recurNthDay != 0) {
$nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay;
$weekday = log($this->recurData, 2);
} else {
$nth = ceil($this->start->mday / 7);
$weekday = $estart->dayOfWeek();
}
// set month from recurrence rule (FIXME: support more than one month)
if ($this->recurMonths) {
$estart->month = $this->recurMonths[0];
}
// Adjust $estart to be the first candidate.
$offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
// Adjust our working date until it's after $after.
$estart->year += $offset - $this->recurInterval;
$count = $offset / $this->recurInterval;
do {
if ($this->recurCount &&
$count++ >= $this->recurCount) {
return false;
}
$estart->year += $this->recurInterval;
$estart->correct();
$next = new Horde_Date($estart);
if ($this->recurNthDay) {
list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y'));
} else {
$next->setNthWeekday($weekday, $nth);
}
if ($next->compareDateTime($after) < 0) {
// We haven't made it past $after yet, try again.
continue;
}
if ($this->hasRecurEnd() &&
$next->compareDateTime($this->recurEnd) > 0) {
// We've gone past the end of recurrence; we can give up
// now.
return false;
}
// We have a candidate to return.
break;
} while (true);
return $next;
}
// We didn't find anything, the recurType was bad, or something else
// went wrong - return false.
return false;
}
/**
* Returns whether this event has any date that matches the recurrence
* rules and is not an exception.
*
* @return boolean True if an active recurrence exists.
*/
function hasActiveRecurrence()
{
if (!$this->hasRecurEnd()) {
return true;
}
$next = $this->nextRecurrence(new Horde_Date($this->start));
while (is_object($next)) {
if (!$this->hasException($next->year, $next->month, $next->mday) &&
!$this->hasCompletion($next->year, $next->month, $next->mday)) {
return true;
}
$next = $this->nextRecurrence(array('year' => $next->year,
'month' => $next->month,
'mday' => $next->mday + 1,
'hour' => $next->hour,
'min' => $next->min,
'sec' => $next->sec));
}
return false;
}
/**
* Returns the next active recurrence.
*
* @param Horde_Date $afterDate Return events after this date.
*
* @return Horde_Date|boolean The date of the next active
* recurrence or false if the event
* has no active recurrence after
* $afterDate.
*/
function nextActiveRecurrence($afterDate)
{
$next = $this->nextRecurrence($afterDate);
while (is_object($next)) {
if (!$this->hasException($next->year, $next->month, $next->mday) &&
!$this->hasCompletion($next->year, $next->month, $next->mday)) {
return $next;
}
$next->mday++;
$next = $this->nextRecurrence($next);
}
return false;
}
/**
* Adds an exception to a recurring event.
*
* @param integer $year The year of the execption.
* @param integer $month The month of the execption.
* @param integer $mday The day of the month of the exception.
*/
function addException($year, $month, $mday)
{
$this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
}
/**
* Deletes an exception from a recurring event.
*
* @param integer $year The year of the execption.
* @param integer $month The month of the execption.
* @param integer $mday The day of the month of the exception.
*/
function deleteException($year, $month, $mday)
{
$key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions);
if ($key !== false) {
unset($this->exceptions[$key]);
}
}
/**
* Checks if an exception exists for a given reccurence of an event.
*
* @param integer $year The year of the reucrance.
* @param integer $month The month of the reucrance.
* @param integer $mday The day of the month of the reucrance.
*
* @return boolean True if an exception exists for the given date.
*/
function hasException($year, $month, $mday)
{
return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
$this->getExceptions());
}
/**
* Retrieves all the exceptions for this event.
*
* @return array Array containing the dates of all the exceptions in
* YYYYMMDD form.
*/
function getExceptions()
{
return $this->exceptions;
}
/**
* Adds a completion to a recurring event.
*
* @param integer $year The year of the execption.
* @param integer $month The month of the execption.
* @param integer $mday The day of the month of the completion.
*/
function addCompletion($year, $month, $mday)
{
$this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
}
/**
* Deletes a completion from a recurring event.
*
* @param integer $year The year of the execption.
* @param integer $month The month of the execption.
* @param integer $mday The day of the month of the completion.
*/
function deleteCompletion($year, $month, $mday)
{
$key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions);
if ($key !== false) {
unset($this->completions[$key]);
}
}
/**
* Checks if a completion exists for a given reccurence of an event.
*
* @param integer $year The year of the reucrance.
* @param integer $month The month of the recurrance.
* @param integer $mday The day of the month of the recurrance.
*
* @return boolean True if a completion exists for the given date.
*/
function hasCompletion($year, $month, $mday)
{
return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
$this->getCompletions());
}
/**
* Retrieves all the completions for this event.
*
* @return array Array containing the dates of all the completions in
* YYYYMMDD form.
*/
function getCompletions()
{
return $this->completions;
}
/**
* Parses a vCalendar 1.0 recurrence rule.
*
* @link http://www.imc.org/pdi/vcal-10.txt
* @link http://www.shuchow.com/vCalAddendum.html
*
* @param string $rrule A vCalendar 1.0 conform RRULE value.
*/
function fromRRule10($rrule)
{
if (!$rrule) {
return;
}
if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) {
// No recurrence data - event does not recur.
$this->setRecurType(HORDE_DATE_RECUR_NONE);
}
// Always default the recurInterval to 1.
$this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1);
$remainder = trim($matches[3]);
switch ($matches[1]) {
case 'D':
$this->setRecurType(HORDE_DATE_RECUR_DAILY);
break;
case 'W':
$this->setRecurType(HORDE_DATE_RECUR_WEEKLY);
if (!empty($remainder)) {
$maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY,
'MO' => HORDE_DATE_MASK_MONDAY,
'TU' => HORDE_DATE_MASK_TUESDAY,
'WE' => HORDE_DATE_MASK_WEDNESDAY,
'TH' => HORDE_DATE_MASK_THURSDAY,
'FR' => HORDE_DATE_MASK_FRIDAY,
'SA' => HORDE_DATE_MASK_SATURDAY);
$mask = 0;
while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) {
$day = trim($matches[0]);
$remainder = substr($remainder, strlen($matches[0]));
$mask |= $maskdays[$day];
}
$this->setRecurOnDay($mask);
} else {
// Recur on the day of the week of the original recurrence.
$maskdays = array(HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY,
HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY,
HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY,
HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY,
HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY,
HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY,
HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY);
$this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
}
break;
case 'MP':
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY);
break;
case 'MD':
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
break;
case 'YM':
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE);
break;
case 'YD':
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY);
break;
}
// We don't support modifiers at the moment, strip them.
while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) {
$remainder = substr($remainder, 1);
}
if (!empty($remainder)) {
if (strpos($remainder, '#') === 0) {
$this->setRecurCount(substr($remainder, 1));
} else {
list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d');
$this->setRecurEnd(new Horde_Date(array('year' => $year,
'month' => $month,
'mday' => $mday)));
}
}
}
/**
* Creates a vCalendar 1.0 recurrence rule.
*
* @link http://www.imc.org/pdi/vcal-10.txt
* @link http://www.shuchow.com/vCalAddendum.html
*
* @param Horde_iCalendar $calendar A Horde_iCalendar object instance.
*
* @return string A vCalendar 1.0 conform RRULE value.
*/
function toRRule10($calendar)
{
switch ($this->recurType) {
case HORDE_DATE_RECUR_NONE:
return '';
case HORDE_DATE_RECUR_DAILY:
$rrule = 'D' . $this->recurInterval;
break;
case HORDE_DATE_RECUR_WEEKLY:
$rrule = 'W' . $this->recurInterval;
$vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
for ($i = 0; $i <= 7 ; ++$i) {
if ($this->recurOnDay(pow(2, $i))) {
$rrule .= ' ' . $vcaldays[$i];
}
}
break;
case HORDE_DATE_RECUR_MONTHLY_DATE:
$rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday);
break;
case HORDE_DATE_RECUR_MONTHLY_WEEKDAY:
$nth_weekday = (int)($this->start->mday / 7);
if (($this->start->mday % 7) > 0) {
$nth_weekday++;
}
$vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
$rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()];
break;
case HORDE_DATE_RECUR_YEARLY_DATE:
$rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month);
break;
case HORDE_DATE_RECUR_YEARLY_DAY:
$rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear();
break;
default:
return '';
}
if ($this->hasRecurEnd()) {
$recurEnd = new Horde_Date($this->recurEnd);
$recurEnd->mday++;
return $rrule . ' ' . $calendar->_exportDateTime($recurEnd);
}
return $rrule . ' #' . (int)$this->getRecurCount();
}
/**
* Parses an iCalendar 2.0 recurrence rule.
*
* @link http://rfc.net/rfc2445.html#s4.3.10
* @link http://rfc.net/rfc2445.html#s4.8.5
* @link http://www.shuchow.com/vCalAddendum.html
*
* @param string $rrule An iCalendar 2.0 conform RRULE value.
*/
function fromRRule20($rrule)
{
// Parse the recurrence rule into keys and values.
$rdata = array();
$parts = explode(';', $rrule);
foreach ($parts as $part) {
list($key, $value) = explode('=', $part, 2);
$rdata[strtoupper($key)] = $value;
}
if (isset($rdata['FREQ'])) {
// Always default the recurInterval to 1.
$this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1);
$maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY,
'MO' => HORDE_DATE_MASK_MONDAY,
'TU' => HORDE_DATE_MASK_TUESDAY,
'WE' => HORDE_DATE_MASK_WEDNESDAY,
'TH' => HORDE_DATE_MASK_THURSDAY,
'FR' => HORDE_DATE_MASK_FRIDAY,
'SA' => HORDE_DATE_MASK_SATURDAY);
switch (strtoupper($rdata['FREQ'])) {
case 'DAILY':
$this->setRecurType(HORDE_DATE_RECUR_DAILY);
break;
case 'WEEKLY':
$this->setRecurType(HORDE_DATE_RECUR_WEEKLY);
if (isset($rdata['BYDAY'])) {
$days = explode(',', $rdata['BYDAY']);
$mask = 0;
foreach ($days as $day) {
$mask |= $maskdays[$day];
}
$this->setRecurOnDay($mask);
} else {
// Recur on the day of the week of the original
// recurrence.
$maskdays = array(
HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY,
HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY,
HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY,
HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY,
HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY,
HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY,
HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY);
$this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
}
break;
case 'MONTHLY':
if (isset($rdata['BYDAY'])) {
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY);
if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
$this->setRecurOnDay($maskdays[$m[2]]);
$this->setRecurNthWeekday($m[1]);
}
} else {
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
}
break;
case 'YEARLY':
if (isset($rdata['BYYEARDAY'])) {
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY);
} elseif (isset($rdata['BYDAY'])) {
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY);
if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
$this->setRecurOnDay($maskdays[$m[2]]);
$this->setRecurNthWeekday($m[1]);
}
if ($rdata['BYMONTH']) {
$months = explode(',', $rdata['BYMONTH']);
$this->setRecurByMonth($months);
}
} else {
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE);
}
break;
}
if (isset($rdata['UNTIL'])) {
list($year, $month, $mday) = sscanf($rdata['UNTIL'],
'%04d%02d%02d');
$this->setRecurEnd(new Horde_Date(array('year' => $year,
'month' => $month,
'mday' => $mday)));
}
if (isset($rdata['COUNT'])) {
$this->setRecurCount($rdata['COUNT']);
}
} else {
// No recurrence data - event does not recur.
$this->setRecurType(HORDE_DATE_RECUR_NONE);
}
}
/**
* Creates an iCalendar 2.0 recurrence rule.
*
* @link http://rfc.net/rfc2445.html#s4.3.10
* @link http://rfc.net/rfc2445.html#s4.8.5
* @link http://www.shuchow.com/vCalAddendum.html
*
* @param Horde_iCalendar $calendar A Horde_iCalendar object instance.
*
* @return string An iCalendar 2.0 conform RRULE value.
*/
function toRRule20($calendar)
{
switch ($this->recurType) {
case HORDE_DATE_RECUR_NONE:
return '';
case HORDE_DATE_RECUR_DAILY:
$rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval;
break;
case HORDE_DATE_RECUR_WEEKLY:
$rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY=';
$vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
for ($i = $flag = 0; $i <= 7 ; ++$i) {
if ($this->recurOnDay(pow(2, $i))) {
if ($flag) {
$rrule .= ',';
}
$rrule .= $vcaldays[$i];
$flag = true;
}
}
break;
case HORDE_DATE_RECUR_MONTHLY_DATE:
$rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval;
break;
case HORDE_DATE_RECUR_MONTHLY_WEEKDAY:
if ($this->recurNthDay != 0) {
$nth_weekday = $this->recurNthDay;
$day_of_week = log($this->recurData, 2);
} else {
$day_of_week = $this->start->dayOfWeek();
$nth_weekday = (int)($this->start->mday / 7);
if (($this->start->mday % 7) > 0) {
$nth_weekday++;
}
}
$vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
$rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval
. ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week];
break;
case HORDE_DATE_RECUR_YEARLY_DATE:
$rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval;
break;
case HORDE_DATE_RECUR_YEARLY_DAY:
$rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
. ';BYYEARDAY=' . $this->start->dayOfYear();
break;
case HORDE_DATE_RECUR_YEARLY_WEEKDAY:
if ($this->recurNthDay != 0) {
$nth_weekday = $this->recurNthDay;
$day_of_week = log($this->recurData, 2);
} else {
$day_of_week = $this->start->dayOfWeek();
$nth_weekday = (int)($this->start->mday / 7);
if (($this->start->mday % 7) > 0) {
$nth_weekday++;
}
}
$months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month;
$vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
$rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
. ';BYDAY='
. $nth_weekday
. $vcaldays[$day_of_week]
. ';BYMONTH=' . $months;
break;
}
if ($this->hasRecurEnd()) {
$recurEnd = new Horde_Date($this->recurEnd);
$recurEnd->mday++;
$rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd);
}
if ($count = $this->getRecurCount()) {
$rrule .= ';COUNT=' . $count;
}
return $rrule;
}
/**
* Parses the recurrence data from a hash.
*
* @param array $hash The hash to convert.
*
* @return boolean True if the hash seemed valid, false otherwise.
*/
function fromHash($hash)
{
if (!isset($hash['interval']) || !isset($hash['interval']) ||
!isset($hash['range-type'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$month2number = array(
'january' => 1,
'february' => 2,
'march' => 3,
'april' => 4,
'may' => 5,
'june' => 6,
'july' => 7,
'august' => 8,
'september' => 9,
'october' => 10,
'november' => 11,
'december' => 12,
);
$this->setRecurInterval((int) $hash['interval']);
$parse_day = false;
$set_daymask = false;
$update_month = false;
$update_daynumber = false;
$update_weekday = false;
$nth_weekday = -1;
switch ($hash['cycle']) {
case 'daily':
$this->setRecurType(HORDE_DATE_RECUR_DAILY);
break;
case 'weekly':
$this->setRecurType(HORDE_DATE_RECUR_WEEKLY);
$parse_day = true;
$set_daymask = true;
break;
case 'monthly':
if (!isset($hash['daynumber'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
switch ($hash['type']) {
case 'daynumber':
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
$update_daynumber = true;
break;
case 'weekday':
$this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY);
$this->setRecurNthWeekday($hash['daynumber']);
$parse_day = true;
$set_daymask = true;
break;
}
break;
case 'yearly':
if (!isset($hash['type'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
switch ($hash['type']) {
case 'monthday':
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE);
$update_month = true;
$update_daynumber = true;
break;
case 'yearday':
if (!isset($hash['month'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY);
// Start counting days in January.
$hash['month'] = 'january';
$update_month = true;
$update_daynumber = true;
break;
case 'weekday':
if (!isset($hash['daynumber'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY);
$this->setRecurNthWeekday($hash['daynumber']);
$parse_day = true;
$set_daymask = true;
if ($hash['month'] && isset($month2number[$hash['month']])) {
$this->setRecurByMonth($month2number[$hash['month']]);
}
break;
}
}
switch ($hash['range-type']) {
case 'number':
if (!isset($hash['range'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$this->setRecurCount((int) $hash['range']);
break;
case 'date':
$recur_end = new Horde_Date($hash['range']);
$recur_end->hour = 23;
$recur_end->min = 59;
$recur_end->sec = 59;
$this->setRecurEnd($recur_end);
break;
}
// Need to parse <day>?
$last_found_day = -1;
if ($parse_day) {
if (!isset($hash['day'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$mask = 0;
$bits = array(
'monday' => HORDE_DATE_MASK_MONDAY,
'tuesday' => HORDE_DATE_MASK_TUESDAY,
'wednesday' => HORDE_DATE_MASK_WEDNESDAY,
'thursday' => HORDE_DATE_MASK_THURSDAY,
'friday' => HORDE_DATE_MASK_FRIDAY,
'saturday' => HORDE_DATE_MASK_SATURDAY,
'sunday' => HORDE_DATE_MASK_SUNDAY,
);
$days = array(
'monday' => HORDE_DATE_MONDAY,
'tuesday' => HORDE_DATE_TUESDAY,
'wednesday' => HORDE_DATE_WEDNESDAY,
'thursday' => HORDE_DATE_THURSDAY,
'friday' => HORDE_DATE_FRIDAY,
'saturday' => HORDE_DATE_SATURDAY,
'sunday' => HORDE_DATE_SUNDAY,
);
foreach ($hash['day'] as $day) {
// Validity check.
if (empty($day) || !isset($bits[$day])) {
continue;
}
$mask |= $bits[$day];
$last_found_day = $days[$day];
}
if ($set_daymask) {
$this->setRecurOnDay($mask);
}
}
if ($update_month || $update_daynumber || $update_weekday) {
if ($update_month) {
if (isset($month2number[$hash['month']])) {
$this->start->month = $month2number[$hash['month']];
}
}
if ($update_daynumber) {
if (!isset($hash['daynumber'])) {
$this->setRecurType(HORDE_DATE_RECUR_NONE);
return false;
}
$this->start->mday = $hash['daynumber'];
}
if ($update_weekday) {
$this->start->setNthWeekday($last_found_day, $nth_weekday);
}
$this->start->correct();
}
// Exceptions.
if (isset($hash['exceptions'])) {
$this->exceptions = $hash['exceptions'];
}
if (isset($hash['completions'])) {
$this->completions = $hash['completions'];
}
return true;
}
/**
* Export this object into a hash.
*
* @return array The recurrence hash.
*/
function toHash()
{
if ($this->getRecurType() == HORDE_DATE_RECUR_NONE) {
return array();
}
$day2number = array(
0 => 'sunday',
1 => 'monday',
2 => 'tuesday',
3 => 'wednesday',
4 => 'thursday',
5 => 'friday',
6 => 'saturday'
);
$month2number = array(
1 => 'january',
2 => 'february',
3 => 'march',
4 => 'april',
5 => 'may',
6 => 'june',
7 => 'july',
8 => 'august',
9 => 'september',
10 => 'october',
11 => 'november',
12 => 'december'
);
$hash = array('interval' => $this->getRecurInterval());
$start = $this->getRecurStart();
switch ($this->getRecurType()) {
case HORDE_DATE_RECUR_DAILY:
$hash['cycle'] = 'daily';
break;
case HORDE_DATE_RECUR_WEEKLY:
$hash['cycle'] = 'weekly';
$bits = array(
'monday' => HORDE_DATE_MASK_MONDAY,
'tuesday' => HORDE_DATE_MASK_TUESDAY,
'wednesday' => HORDE_DATE_MASK_WEDNESDAY,
'thursday' => HORDE_DATE_MASK_THURSDAY,
'friday' => HORDE_DATE_MASK_FRIDAY,
'saturday' => HORDE_DATE_MASK_SATURDAY,
'sunday' => HORDE_DATE_MASK_SUNDAY,
);
$days = array();
foreach($bits as $name => $bit) {
if ($this->recurOnDay($bit)) {
$days[] = $name;
}
}
$hash['day'] = $days;
break;
case HORDE_DATE_RECUR_MONTHLY_DATE:
$hash['cycle'] = 'monthly';
$hash['type'] = 'daynumber';
$hash['daynumber'] = $start->mday;
break;
case HORDE_DATE_RECUR_MONTHLY_WEEKDAY:
$hash['cycle'] = 'monthly';
$hash['type'] = 'weekday';
$hash['daynumber'] = $start->weekOfMonth();
$hash['day'] = array ($day2number[$start->dayOfWeek()]);
break;
case HORDE_DATE_RECUR_YEARLY_DATE:
$hash['cycle'] = 'yearly';
$hash['type'] = 'monthday';
$hash['daynumber'] = $start->mday;
$hash['month'] = $month2number[$start->month];
break;
case HORDE_DATE_RECUR_YEARLY_DAY:
$hash['cycle'] = 'yearly';
$hash['type'] = 'yearday';
$hash['daynumber'] = $start->dayOfYear();
break;
case HORDE_DATE_RECUR_YEARLY_WEEKDAY:
$hash['cycle'] = 'yearly';
$hash['type'] = 'weekday';
$hash['daynumber'] = $start->weekOfMonth();
$hash['day'] = array ($day2number[$start->dayOfWeek()]);
$hash['month'] = $month2number[$start->month];
}
if ($this->hasRecurCount()) {
$hash['range-type'] = 'number';
$hash['range'] = $this->getRecurCount();
} elseif ($this->hasRecurEnd()) {
$date = $this->getRecurEnd();
$hash['range-type'] = 'date';
$hash['range'] = $date->datestamp();
} else {
$hash['range-type'] = 'none';
$hash['range'] = '';
}
// Recurrence exceptions
$hash['exceptions'] = $this->exceptions;
$hash['completions'] = $this->completions;
return $hash;
}
}
diff --git a/plugins/calendar/lib/Horde_iCalendar.php b/plugins/calendar/lib/Horde_iCalendar.php
new file mode 100644
index 00000000..5ba0f4c8
--- /dev/null
+++ b/plugins/calendar/lib/Horde_iCalendar.php
@@ -0,0 +1,3284 @@
+<?php
+
+require_once(dirname(__FILE__) . '/Horde_Date.php');
+
+
+$GLOBALS['_HORDE_STRING_CHARSET'] = 'iso-8859-1';
+
+/**
+ * The String:: class provides static methods for charset and locale safe
+ * string manipulation.
+ *
+ * $Horde: framework/Util/String.php,v 1.43.6.38 2009-09-15 16:36:14 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @since Horde 3.0
+ * @package Horde_Util
+ */
+class String {
+
+ /**
+ * Caches the result of extension_loaded() calls.
+ *
+ * @param string $ext The extension name.
+ *
+ * @return boolean Is the extension loaded?
+ *
+ * @see Util::extensionExists()
+ */
+ function extensionExists($ext)
+ {
+ static $cache = array();
+
+ if (!isset($cache[$ext])) {
+ $cache[$ext] = extension_loaded($ext);
+ }
+
+ return $cache[$ext];
+ }
+
+ /**
+ * Sets a default charset that the String:: methods will use if none is
+ * explicitly specified.
+ *
+ * @param string $charset The charset to use as the default one.
+ */
+ function setDefaultCharset($charset)
+ {
+ $GLOBALS['_HORDE_STRING_CHARSET'] = $charset;
+ if (String::extensionExists('mbstring') &&
+ function_exists('mb_regex_encoding')) {
+ $old_error = error_reporting(0);
+ mb_regex_encoding(String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ }
+ }
+
+ /**
+ * Converts a string from one charset to another.
+ *
+ * Works only if either the iconv or the mbstring extension
+ * are present and best if both are available.
+ * The original string is returned if conversion failed or none
+ * of the extensions were available.
+ *
+ * @param mixed $input The data to be converted. If $input is an an array,
+ * the array's values get converted recursively.
+ * @param string $from The string's current charset.
+ * @param string $to The charset to convert the string to. If not
+ * specified, the global variable
+ * $_HORDE_STRING_CHARSET will be used.
+ *
+ * @return mixed The converted input data.
+ */
+ function convertCharset($input, $from, $to = null)
+ {
+ /* Don't bother converting numbers. */
+ if (is_numeric($input)) {
+ return $input;
+ }
+
+ /* Get the user's default character set if none passed in. */
+ if (is_null($to)) {
+ $to = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+
+ /* If the from and to character sets are identical, return now. */
+ if ($from == $to) {
+ return $input;
+ }
+ $from = String::lower($from);
+ $to = String::lower($to);
+ if ($from == $to) {
+ return $input;
+ }
+
+ if (is_array($input)) {
+ $tmp = array();
+ reset($input);
+ while (list($key, $val) = each($input)) {
+ $tmp[String::_convertCharset($key, $from, $to)] = String::convertCharset($val, $from, $to);
+ }
+ return $tmp;
+ }
+ if (is_object($input)) {
+ // PEAR_Error objects are almost guaranteed to contain recursion,
+ // which will cause a segfault in PHP. We should never reach
+ // this line, but add a check and a log message to help the devs
+ // track down and fix this issue.
+ if (is_a($input, 'PEAR_Error')) {
+ Horde::logMessage('Called convertCharset() on a PEAR_Error object. ' . print_r($input, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return '';
+ }
+ $vars = get_object_vars($input);
+ while (list($key, $val) = each($vars)) {
+ $input->$key = String::convertCharset($val, $from, $to);
+ }
+ return $input;
+ }
+
+ if (!is_string($input)) {
+ return $input;
+ }
+
+ return String::_convertCharset($input, $from, $to);
+ }
+
+ /**
+ * Internal function used to do charset conversion.
+ *
+ * @access private
+ *
+ * @param string $input See String::convertCharset().
+ * @param string $from See String::convertCharset().
+ * @param string $to See String::convertCharset().
+ *
+ * @return string The converted string.
+ */
+ function _convertCharset($input, $from, $to)
+ {
+ $output = '';
+ $from_check = (($from == 'iso-8859-1') || ($from == 'us-ascii'));
+ $to_check = (($to == 'iso-8859-1') || ($to == 'us-ascii'));
+
+ /* Use utf8_[en|de]code() if possible and if the string isn't too
+ * large (less than 16 MB = 16 * 1024 * 1024 = 16777216 bytes) - these
+ * functions use more memory. */
+ if (strlen($input) < 16777216 || !(String::extensionExists('iconv') || String::extensionExists('mbstring'))) {
+ if ($from_check && ($to == 'utf-8')) {
+ return utf8_encode($input);
+ }
+
+ if (($from == 'utf-8') && $to_check) {
+ return utf8_decode($input);
+ }
+ }
+
+ /* First try iconv with transliteration. */
+ if (($from != 'utf7-imap') &&
+ ($to != 'utf7-imap') &&
+ String::extensionExists('iconv')) {
+ /* We need to tack an extra character temporarily because of a bug
+ * in iconv() if the last character is not a 7 bit ASCII
+ * character. */
+ $oldTrackErrors = ini_set('track_errors', 1);
+ unset($php_errormsg);
+ $output = @iconv($from, $to . '//TRANSLIT', $input . 'x');
+ $output = (isset($php_errormsg)) ? false : String::substr($output, 0, -1, $to);
+ ini_set('track_errors', $oldTrackErrors);
+ }
+
+ /* Next try mbstring. */
+ if (!$output && String::extensionExists('mbstring')) {
+ $old_error = error_reporting(0);
+ $output = mb_convert_encoding($input, $to, String::_mbstringCharset($from));
+ error_reporting($old_error);
+ }
+
+ /* At last try imap_utf7_[en|de]code if appropriate. */
+ if (!$output && String::extensionExists('imap')) {
+ if ($from_check && ($to == 'utf7-imap')) {
+ return @imap_utf7_encode($input);
+ }
+ if (($from == 'utf7-imap') && $to_check) {
+ return @imap_utf7_decode($input);
+ }
+ }
+
+ return (!$output) ? $input : $output;
+ }
+
+ /**
+ * Makes a string lowercase.
+ *
+ * @param string $string The string to be converted.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset If $locale is true, the charset to use when
+ * converting. If not provided the current charset.
+ *
+ * @return string The string with lowercase characters
+ */
+ function lower($string, $locale = false, $charset = null)
+ {
+ static $lowers;
+
+ if ($locale) {
+ /* The existence of mb_strtolower() depends on the platform. */
+ if (String::extensionExists('mbstring') &&
+ function_exists('mb_strtolower')) {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $old_error = error_reporting(0);
+ $ret = mb_strtolower($string, String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+ return strtolower($string);
+ }
+
+ if (!isset($lowers)) {
+ $lowers = array();
+ }
+ if (!isset($lowers[$string])) {
+ $language = setlocale(LC_CTYPE, 0);
+ setlocale(LC_CTYPE, 'C');
+ $lowers[$string] = strtolower($string);
+ setlocale(LC_CTYPE, $language);
+ }
+
+ return $lowers[$string];
+ }
+
+ /**
+ * Makes a string uppercase.
+ *
+ * @param string $string The string to be converted.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset If $locale is true, the charset to use when
+ * converting. If not provided the current charset.
+ *
+ * @return string The string with uppercase characters
+ */
+ function upper($string, $locale = false, $charset = null)
+ {
+ static $uppers;
+
+ if ($locale) {
+ /* The existence of mb_strtoupper() depends on the
+ * platform. */
+ if (function_exists('mb_strtoupper')) {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $old_error = error_reporting(0);
+ $ret = mb_strtoupper($string, String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+ return strtoupper($string);
+ }
+
+ if (!isset($uppers)) {
+ $uppers = array();
+ }
+ if (!isset($uppers[$string])) {
+ $language = setlocale(LC_CTYPE, 0);
+ setlocale(LC_CTYPE, 'C');
+ $uppers[$string] = strtoupper($string);
+ setlocale(LC_CTYPE, $language);
+ }
+
+ return $uppers[$string];
+ }
+
+ /**
+ * Returns a string with the first letter capitalized if it is
+ * alphabetic.
+ *
+ * @param string $string The string to be capitalized.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset The charset to use, defaults to current charset.
+ *
+ * @return string The capitalized string.
+ */
+ function ucfirst($string, $locale = false, $charset = null)
+ {
+ if ($locale) {
+ $first = String::substr($string, 0, 1, $charset);
+ if (String::isAlpha($first, $charset)) {
+ $string = String::upper($first, true, $charset) . String::substr($string, 1, null, $charset);
+ }
+ } else {
+ $string = String::upper(substr($string, 0, 1), false) . substr($string, 1);
+ }
+ return $string;
+ }
+
+ /**
+ * Returns part of a string.
+ *
+ * @param string $string The string to be converted.
+ * @param integer $start The part's start position, zero based.
+ * @param integer $length The part's length.
+ * @param string $charset The charset to use when calculating the part's
+ * position and length, defaults to current
+ * charset.
+ *
+ * @return string The string's part.
+ */
+ function substr($string, $start, $length = null, $charset = null)
+ {
+ if (is_null($length)) {
+ $length = String::length($string, $charset) - $start;
+ }
+
+ if ($length == 0) {
+ return '';
+ }
+
+ /* Try iconv. */
+ if (function_exists('iconv_substr')) {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+
+ $old_error = error_reporting(0);
+ $ret = iconv_substr($string, $start, $length, $charset);
+ error_reporting($old_error);
+ /* iconv_substr() returns false on failure. */
+ if ($ret !== false) {
+ return $ret;
+ }
+ }
+
+ /* Try mbstring. */
+ if (String::extensionExists('mbstring')) {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $old_error = error_reporting(0);
+ $ret = mb_substr($string, $start, $length, String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ /* mb_substr() returns empty string on failure. */
+ if (strlen($ret)) {
+ return $ret;
+ }
+ }
+
+ return substr($string, $start, $length);
+ }
+
+ /**
+ * Returns the character (not byte) length of a string.
+ *
+ * @param string $string The string to return the length of.
+ * @param string $charset The charset to use when calculating the string's
+ * length.
+ *
+ * @return string The string's part.
+ */
+ function length($string, $charset = null)
+ {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $charset = String::lower($charset);
+ if ($charset == 'utf-8' || $charset == 'utf8') {
+ return strlen(utf8_decode($string));
+ }
+ if (String::extensionExists('mbstring')) {
+ $old_error = error_reporting(0);
+ $ret = mb_strlen($string, String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+ return strlen($string);
+ }
+
+ /**
+ * Returns the numeric position of the first occurrence of $needle
+ * in the $haystack string.
+ *
+ * @param string $haystack The string to search through.
+ * @param string $needle The string to search for.
+ * @param integer $offset Allows to specify which character in haystack
+ * to start searching.
+ * @param string $charset The charset to use when searching for the
+ * $needle string.
+ *
+ * @return integer The position of first occurrence.
+ */
+ function pos($haystack, $needle, $offset = 0, $charset = null)
+ {
+ if (String::extensionExists('mbstring')) {
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $track_errors = ini_set('track_errors', 1);
+ $old_error = error_reporting(0);
+ $ret = mb_strpos($haystack, $needle, $offset, String::_mbstringCharset($charset));
+ error_reporting($old_error);
+ ini_set('track_errors', $track_errors);
+ if (!isset($php_errormsg)) {
+ return $ret;
+ }
+ }
+ return strpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Returns a string padded to a certain length with another string.
+ *
+ * This method behaves exactly like str_pad but is multibyte safe.
+ *
+ * @param string $input The string to be padded.
+ * @param integer $length The length of the resulting string.
+ * @param string $pad The string to pad the input string with. Must
+ * be in the same charset like the input string.
+ * @param const $type The padding type. One of STR_PAD_LEFT,
+ * STR_PAD_RIGHT, or STR_PAD_BOTH.
+ * @param string $charset The charset of the input and the padding
+ * strings.
+ *
+ * @return string The padded string.
+ */
+ function pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT,
+ $charset = null)
+ {
+ $mb_length = String::length($input, $charset);
+ $sb_length = strlen($input);
+ $pad_length = String::length($pad, $charset);
+
+ /* Return if we already have the length. */
+ if ($mb_length >= $length) {
+ return $input;
+ }
+
+ /* Shortcut for single byte strings. */
+ if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
+ return str_pad($input, $length, $pad, $type);
+ }
+
+ switch ($type) {
+ case STR_PAD_LEFT:
+ $left = $length - $mb_length;
+ $output = String::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) . $input;
+ break;
+ case STR_PAD_BOTH:
+ $left = floor(($length - $mb_length) / 2);
+ $right = ceil(($length - $mb_length) / 2);
+ $output = String::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) .
+ $input .
+ String::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset);
+ break;
+ case STR_PAD_RIGHT:
+ $right = $length - $mb_length;
+ $output = $input . String::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset);
+ break;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Wraps the text of a message.
+ *
+ * @since Horde 3.2
+ *
+ * @param string $string String containing the text to wrap.
+ * @param integer $width Wrap the string at this number of
+ * characters.
+ * @param string $break Character(s) to use when breaking lines.
+ * @param boolean $cut Whether to cut inside words if a line
+ * can't be wrapped.
+ * @param string $charset Character set to use when breaking lines.
+ * @param boolean $line_folding Whether to apply line folding rules per
+ * RFC 822 or similar. The correct break
+ * characters including leading whitespace
+ * have to be specified too.
+ *
+ * @return string String containing the wrapped text.
+ */
+ function wordwrap($string, $width = 75, $break = "\n", $cut = false,
+ $charset = null, $line_folding = false)
+ {
+ /* Get the user's default character set if none passed in. */
+ if (is_null($charset)) {
+ $charset = $GLOBALS['_HORDE_STRING_CHARSET'];
+ }
+ $charset = String::_mbstringCharset($charset);
+ $string = String::convertCharset($string, $charset, 'utf-8');
+ $wrapped = '';
+
+ while (String::length($string, 'utf-8') > $width) {
+ $line = String::substr($string, 0, $width, 'utf-8');
+ $string = String::substr($string, String::length($line, 'utf-8'), null, 'utf-8');
+ // Make sure didn't cut a word, unless we want hard breaks anyway.
+ if (!$cut && preg_match('/^(.+?)((\s|\r?\n).*)/us', $string, $match)) {
+ $line .= $match[1];
+ $string = $match[2];
+ }
+ // Wrap at existing line breaks.
+ if (preg_match('/^(.*?)(\r?\n)(.*)$/u', $line, $match)) {
+ $wrapped .= $match[1] . $match[2];
+ $string = $match[3] . $string;
+ continue;
+ }
+ // Wrap at the last colon or semicolon followed by a whitespace if
+ // doing line folding.
+ if ($line_folding &&
+ preg_match('/^(.*?)(;|:)(\s+.*)$/u', $line, $match)) {
+ $wrapped .= $match[1] . $match[2] . $break;
+ $string = $match[3] . $string;
+ continue;
+ }
+ // Wrap at the last whitespace of $line.
+ if ($line_folding) {
+ $sub = '(.+[^\s])';
+ } else {
+ $sub = '(.*)';
+ }
+ if (preg_match('/^' . $sub . '(\s+)(.*)$/u', $line, $match)) {
+ $wrapped .= $match[1] . $break;
+ $string = ($line_folding ? $match[2] : '') . $match[3] . $string;
+ continue;
+ }
+ // Hard wrap if necessary.
+ if ($cut) {
+ $wrapped .= $line . $break;
+ continue;
+ }
+ $wrapped .= $line;
+ }
+
+ return String::convertCharset($wrapped . $string, 'utf-8', $charset);
+ }
+
+ /**
+ * Wraps the text of a message.
+ *
+ * @param string $text String containing the text to wrap.
+ * @param integer $length Wrap $text at this number of characters.
+ * @param string $break_char Character(s) to use when breaking lines.
+ * @param string $charset Character set to use when breaking lines.
+ * @param boolean $quote Ignore lines that are wrapped with the '>'
+ * character (RFC 2646)? If true, we don't
+ * remove any padding whitespace at the end of
+ * the string.
+ *
+ * @return string String containing the wrapped text.
+ */
+ function wrap($text, $length = 80, $break_char = "\n", $charset = null,
+ $quote = false)
+ {
+ $paragraphs = array();
+
+ foreach (preg_split('/\r?\n/', $text) as $input) {
+ if ($quote && (strpos($input, '>') === 0)) {
+ $line = $input;
+ } else {
+ /* We need to handle the Usenet-style signature line
+ * separately; since the space after the two dashes is
+ * REQUIRED, we don't want to trim the line. */
+ if ($input != '-- ') {
+ $input = rtrim($input);
+ }
+ $line = String::wordwrap($input, $length, $break_char, false, $charset);
+ }
+
+ $paragraphs[] = $line;
+ }
+
+ return implode($break_char, $paragraphs);
+ }
+
+ /**
+ * Returns true if the every character in the parameter is an alphabetic
+ * character.
+ *
+ * @param $string The string to test.
+ * @param $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was alphabetic only.
+ */
+ function isAlpha($string, $charset = null)
+ {
+ if (!String::extensionExists('mbstring')) {
+ return ctype_alpha($string);
+ }
+
+ $charset = String::_mbstringCharset($charset);
+ $old_charset = mb_regex_encoding();
+ $old_error = error_reporting(0);
+
+ if ($charset != $old_charset) {
+ mb_regex_encoding($charset);
+ }
+ $alpha = !mb_ereg_match('[^[:alpha:]]', $string);
+ if ($charset != $old_charset) {
+ mb_regex_encoding($old_charset);
+ }
+
+ error_reporting($old_error);
+
+ return $alpha;
+ }
+
+ /**
+ * Returns true if ever character in the parameter is a lowercase letter in
+ * the current locale.
+ *
+ * @param $string The string to test.
+ * @param $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was lowercase.
+ */
+ function isLower($string, $charset = null)
+ {
+ return ((String::lower($string, true, $charset) === $string) &&
+ String::isAlpha($string, $charset));
+ }
+
+ /**
+ * Returns true if every character in the parameter is an uppercase letter
+ * in the current locale.
+ *
+ * @param string $string The string to test.
+ * @param string $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was uppercase.
+ */
+ function isUpper($string, $charset = null)
+ {
+ return ((String::upper($string, true, $charset) === $string) &&
+ String::isAlpha($string, $charset));
+ }
+
+ /**
+ * Performs a multibyte safe regex match search on the text provided.
+ *
+ * @since Horde 3.1
+ *
+ * @param string $text The text to search.
+ * @param array $regex The regular expressions to use, without perl
+ * regex delimiters (e.g. '/' or '|').
+ * @param string $charset The character set of the text.
+ *
+ * @return array The matches array from the first regex that matches.
+ */
+ function regexMatch($text, $regex, $charset = null)
+ {
+ if (!empty($charset)) {
+ $regex = String::convertCharset($regex, $charset, 'utf-8');
+ $text = String::convertCharset($text, $charset, 'utf-8');
+ }
+
+ $matches = array();
+ foreach ($regex as $val) {
+ if (preg_match('/' . $val . '/u', $text, $matches)) {
+ break;
+ }
+ }
+
+ if (!empty($charset)) {
+ $matches = String::convertCharset($matches, 'utf-8', $charset);
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Workaround charsets that don't work with mbstring functions.
+ *
+ * @access private
+ *
+ * @param string $charset The original charset.
+ *
+ * @return string The charset to use with mbstring functions.
+ */
+ function _mbstringCharset($charset)
+ {
+ /* mbstring functions do not handle the 'ks_c_5601-1987' &
+ * 'ks_c_5601-1989' charsets. However, these charsets are used, for
+ * example, by various versions of Outlook to send Korean characters.
+ * Use UHC (CP949) encoding instead. See, e.g.,
+ * http://lists.w3.org/Archives/Public/ietf-charsets/2001AprJun/0030.html */
+ if (in_array(String::lower($charset), array('ks_c_5601-1987', 'ks_c_5601-1989'))) {
+ $charset = 'UHC';
+ }
+
+ return $charset;
+ }
+
+}
+
+
+
+/**
+ * @package Horde_iCalendar
+ */
+
+/**
+ * String package
+ */
+
+
+
+/**
+ * Class representing iCalendar files.
+ *
+ * $Horde: framework/iCalendar/iCalendar.php,v 1.57.4.81 2010-11-10 14:34:25 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar {
+
+ /**
+ * The parent (containing) iCalendar object.
+ *
+ * @var Horde_iCalendar
+ */
+ var $_container = false;
+
+ /**
+ * The name/value pairs of attributes for this object (UID,
+ * DTSTART, etc.). Which are present depends on the object and on
+ * what kind of component it is.
+ *
+ * @var array
+ */
+ var $_attributes = array();
+
+ /**
+ * Any children (contained) iCalendar components of this object.
+ *
+ * @var array
+ */
+ var $_components = array();
+
+ /**
+ * According to RFC 2425, we should always use CRLF-terminated lines.
+ *
+ * @var string
+ */
+ var $_newline = "\r\n";
+
+ /**
+ * iCalendar format version (different behavior for 1.0 and 2.0
+ * especially with recurring events).
+ *
+ * @var string
+ */
+ var $_version;
+
+ function Horde_iCalendar($version = '2.0')
+ {
+ $this->_version = $version;
+ $this->setAttribute('VERSION', $version);
+ }
+
+ /**
+ * Return a reference to a new component.
+ *
+ * @param string $type The type of component to return
+ * @param Horde_iCalendar $container A container that this component
+ * will be associated with.
+ *
+ * @return object Reference to a Horde_iCalendar_* object as specified.
+ *
+ * @static
+ */
+ function &newComponent($type, &$container)
+ {
+ $type = String::lower($type);
+ $class = 'Horde_iCalendar_' . $type;
+ if (!class_exists($class)) {
+ include 'Horde/iCalendar/' . $type . '.php';
+ }
+ if (class_exists($class)) {
+ $component = new $class();
+ if ($container !== false) {
+ $component->_container = &$container;
+ // Use version of container, not default set by component
+ // constructor.
+ $component->_version = $container->_version;
+ }
+ } else {
+ // Should return an dummy x-unknown type class here.
+ $component = false;
+ }
+
+ return $component;
+ }
+
+ /**
+ * Sets the value of an attribute.
+ *
+ * @param string $name The name of the attribute.
+ * @param string $value The value of the attribute.
+ * @param array $params Array containing any addition parameters for
+ * this attribute.
+ * @param boolean $append True to append the attribute, False to replace
+ * the first matching attribute found.
+ * @param array $values Array representation of $value. For
+ * comma/semicolon seperated lists of values. If
+ * not set use $value as single array element.
+ */
+ function setAttribute($name, $value, $params = array(), $append = true,
+ $values = false)
+ {
+ // Make sure we update the internal format version if
+ // setAttribute('VERSION', ...) is called.
+ if ($name == 'VERSION') {
+ $this->_version = $value;
+ if ($this->_container !== false) {
+ $this->_container->_version = $value;
+ }
+ }
+
+ if (!$values) {
+ $values = array($value);
+ }
+ $found = false;
+ if (!$append) {
+ foreach (array_keys($this->_attributes) as $key) {
+ if ($this->_attributes[$key]['name'] == String::upper($name)) {
+ $this->_attributes[$key]['params'] = $params;
+ $this->_attributes[$key]['value'] = $value;
+ $this->_attributes[$key]['values'] = $values;
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if ($append || !$found) {
+ $this->_attributes[] = array(
+ 'name' => String::upper($name),
+ 'params' => $params,
+ 'value' => $value,
+ 'values' => $values
+ );
+ }
+ }
+
+ /**
+ * Sets parameter(s) for an (already existing) attribute. The
+ * parameter set is merged into the existing set.
+ *
+ * @param string $name The name of the attribute.
+ * @param array $params Array containing any additional parameters for
+ * this attribute.
+ * @return boolean True on success, false if no attribute $name exists.
+ */
+ function setParameter($name, $params = array())
+ {
+ $keys = array_keys($this->_attributes);
+ foreach ($keys as $key) {
+ if ($this->_attributes[$key]['name'] == $name) {
+ $this->_attributes[$key]['params'] =
+ array_merge($this->_attributes[$key]['params'], $params);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the value of an attribute.
+ *
+ * @param string $name The name of the attribute.
+ * @param boolean $params Return the parameters for this attribute instead
+ * of its value.
+ *
+ * @return mixed (object) PEAR_Error if the attribute does not exist.
+ * (string) The value of the attribute.
+ * (array) The parameters for the attribute or
+ * multiple values for an attribute.
+ */
+ function getAttribute($name, $params = false)
+ {
+ $result = array();
+ foreach ($this->_attributes as $attribute) {
+ if ($attribute['name'] == $name) {
+ if ($params) {
+ $result[] = $attribute['params'];
+ } else {
+ $result[] = $attribute['value'];
+ }
+ }
+ }
+ if (!count($result)) {
+ require_once 'PEAR.php';
+ return PEAR::raiseError('Attribute "' . $name . '" Not Found');
+ } if (count($result) == 1 && !$params) {
+ return $result[0];
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * Gets the values of an attribute as an array. Multiple values
+ * are possible due to:
+ *
+ * a) multiplce occurences of 'name'
+ * b) (unsecapd) comma seperated lists.
+ *
+ * So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY')
+ * will return array('a', 'b', 'c').
+ *
+ * @param string $name The name of the attribute.
+ * @return mixed (object) PEAR_Error if the attribute does not exist.
+ * (array) Multiple values for an attribute.
+ */
+ function getAttributeValues($name)
+ {
+ $result = array();
+ foreach ($this->_attributes as $attribute) {
+ if ($attribute['name'] == $name) {
+ $result = array_merge($attribute['values'], $result);
+ }
+ }
+ if (!count($result)) {
+ return PEAR::raiseError('Attribute "' . $name . '" Not Found');
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the value of an attribute, or a specified default value
+ * if the attribute does not exist.
+ *
+ * @param string $name The name of the attribute.
+ * @param mixed $default What to return if the attribute specified by
+ * $name does not exist.
+ *
+ * @return mixed (string) The value of $name.
+ * (mixed) $default if $name does not exist.
+ */
+ function getAttributeDefault($name, $default = '')
+ {
+ $value = $this->getAttribute($name);
+ return is_a($value, 'PEAR_Error') ? $default : $value;
+ }
+
+ /**
+ * Remove all occurences of an attribute.
+ *
+ * @param string $name The name of the attribute.
+ */
+ function removeAttribute($name)
+ {
+ $keys = array_keys($this->_attributes);
+ foreach ($keys as $key) {
+ if ($this->_attributes[$key]['name'] == $name) {
+ unset($this->_attributes[$key]);
+ }
+ }
+ }
+
+ /**
+ * Get attributes for all tags or for a given tag.
+ *
+ * @param string $tag Return attributes for this tag, or all attributes if
+ * not given.
+ *
+ * @return array An array containing all the attributes and their types.
+ */
+ function getAllAttributes($tag = false)
+ {
+ if ($tag === false) {
+ return $this->_attributes;
+ }
+ $result = array();
+ foreach ($this->_attributes as $attribute) {
+ if ($attribute['name'] == $tag) {
+ $result[] = $attribute;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Add a vCalendar component (eg vEvent, vTimezone, etc.).
+ *
+ * @param Horde_iCalendar $component Component (subclass) to add.
+ */
+ function addComponent($component)
+ {
+ if (is_a($component, 'Horde_iCalendar')) {
+ $component->_container = &$this;
+ $this->_components[] = &$component;
+ }
+ }
+
+ /**
+ * Retrieve all the components.
+ *
+ * @return array Array of Horde_iCalendar objects.
+ */
+ function getComponents()
+ {
+ return $this->_components;
+ }
+
+ function getType()
+ {
+ return 'vcalendar';
+ }
+
+ /**
+ * Return the classes (entry types) we have.
+ *
+ * @return array Hash with class names Horde_iCalendar_xxx as keys
+ * and number of components of this class as value.
+ */
+ function getComponentClasses()
+ {
+ $r = array();
+ foreach ($this->_components as $c) {
+ $cn = strtolower(get_class($c));
+ if (empty($r[$cn])) {
+ $r[$cn] = 1;
+ } else {
+ $r[$cn]++;
+ }
+ }
+
+ return $r;
+ }
+
+ /**
+ * Number of components in this container.
+ *
+ * @return integer Number of components in this container.
+ */
+ function getComponentCount()
+ {
+ return count($this->_components);
+ }
+
+ /**
+ * Retrieve a specific component.
+ *
+ * @param integer $idx The index of the object to retrieve.
+ *
+ * @return mixed (boolean) False if the index does not exist.
+ * (Horde_iCalendar_*) The requested component.
+ */
+ function getComponent($idx)
+ {
+ if (isset($this->_components[$idx])) {
+ return $this->_components[$idx];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Locates the first child component of the specified class, and returns a
+ * reference to it.
+ *
+ * @param string $type The type of component to find.
+ *
+ * @return boolean|Horde_iCalendar_* False if no subcomponent of the
+ * specified class exists or a reference
+ * to the requested component.
+ */
+ function &findComponent($childclass)
+ {
+ $childclass = 'Horde_iCalendar_' . String::lower($childclass);
+ $keys = array_keys($this->_components);
+ foreach ($keys as $key) {
+ if (is_a($this->_components[$key], $childclass)) {
+ return $this->_components[$key];
+ }
+ }
+
+ $component = false;
+ return $component;
+ }
+
+ /**
+ * Locates the first matching child component of the specified class, and
+ * returns a reference to it.
+ *
+ * @param string $childclass The type of component to find.
+ * @param string $attribute This attribute must be set in the component
+ * for it to match.
+ * @param string $value Optional value that $attribute must match.
+ *
+ * @return boolean|Horde_iCalendar_* False if no matching subcomponent of
+ * the specified class exists, or a
+ * reference to the requested component.
+ */
+ function &findComponentByAttribute($childclass, $attribute, $value = null)
+ {
+ $childclass = 'Horde_iCalendar_' . String::lower($childclass);
+ $keys = array_keys($this->_components);
+ foreach ($keys as $key) {
+ if (is_a($this->_components[$key], $childclass)) {
+ $attr = $this->_components[$key]->getAttribute($attribute);
+ if (is_a($attr, 'PEAR_Error')) {
+ continue;
+ }
+ if ($value !== null && $value != $attr) {
+ continue;
+ }
+ return $this->_components[$key];
+ }
+ }
+
+ $component = false;
+ return $component;
+ }
+
+ /**
+ * Clears the iCalendar object (resets the components and attributes
+ * arrays).
+ */
+ function clear()
+ {
+ $this->_components = array();
+ $this->_attributes = array();
+ }
+
+ /**
+ * Checks if entry is vcalendar 1.0, vcard 2.1 or vnote 1.1.
+ *
+ * These 'old' formats are defined by www.imc.org. The 'new' (non-old)
+ * formats icalendar 2.0 and vcard 3.0 are defined in rfc2426 and rfc2445
+ * respectively.
+ *
+ * @since Horde 3.1.2
+ */
+ function isOldFormat()
+ {
+ if ($this->getType() == 'vcard') {
+ return ($this->_version < 3);
+ }
+ if ($this->getType() == 'vNote') {
+ return ($this->_version < 2);
+ }
+ if ($this->_version >= 2) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Export as vCalendar format.
+ */
+ function exportvCalendar()
+ {
+ // Default values.
+ $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library' . (defined('HORDE_VERSION') ? ', Horde ' . constant('HORDE_VERSION') : '') . '//EN';
+ $requiredAttributes['METHOD'] = 'PUBLISH';
+
+ foreach ($requiredAttributes as $name => $default_value) {
+ if (is_a($this->getattribute($name), 'PEAR_Error')) {
+ $this->setAttribute($name, $default_value);
+ }
+ }
+
+ return $this->_exportvData('VCALENDAR');
+ }
+
+ /**
+ * Export this entry as a hash array with tag names as keys.
+ *
+ * @param boolean $paramsInKeys
+ * If false, the operation can be quite lossy as the
+ * parameters are ignored when building the array keys.
+ * So if you export a vcard with
+ * LABEL;TYPE=WORK:foo
+ * LABEL;TYPE=HOME:bar
+ * the resulting hash contains only one label field!
+ * If set to true, array keys look like 'LABEL;TYPE=WORK'
+ * @return array A hash array with tag names as keys.
+ */
+ function toHash($paramsInKeys = false)
+ {
+ $hash = array();
+ foreach ($this->_attributes as $a) {
+ $k = $a['name'];
+ if ($paramsInKeys && is_array($a['params'])) {
+ foreach ($a['params'] as $p => $v) {
+ $k .= ";$p=$v";
+ }
+ }
+ $hash[$k] = $a['value'];
+ }
+
+ return $hash;
+ }
+
+ /**
+ * Parses a string containing vCalendar data.
+ *
+ * @todo This method doesn't work well at all, if $base is VCARD.
+ *
+ * @param string $text The data to parse.
+ * @param string $base The type of the base object.
+ * @param string $charset The encoding charset for $text. Defaults to
+ * utf-8 for new format, iso-8859-1 for old format.
+ * @param boolean $clear If true clears the iCal object before parsing.
+ *
+ * @return boolean True on successful import, false otherwise.
+ */
+ function parsevCalendar($text, $base = 'VCALENDAR', $charset = null,
+ $clear = true)
+ {
+ if ($clear) {
+ $this->clear();
+ }
+ if (preg_match('/^BEGIN:' . $base . '(.*)^END:' . $base . '/ism', $text, $matches)) {
+ $container = true;
+ $vCal = $matches[1];
+ } else {
+ // Text isn't enclosed in BEGIN:VCALENDAR
+ // .. END:VCALENDAR. We'll try to parse it anyway.
+ $container = false;
+ $vCal = $text;
+ }
+ $vCal = trim($vCal);
+
+ // Extract all subcomponents.
+ $matches = $components = null;
+ if (preg_match_all('/^BEGIN:(.*)(\r\n|\r|\n)(.*)^END:\1/Uims', $vCal, $components)) {
+ foreach ($components[0] as $key => $data) {
+ // Remove from the vCalendar data.
+ $vCal = str_replace($data, '', $vCal);
+ }
+ } elseif (!$container) {
+ return false;
+ }
+
+ // Unfold "quoted printable" folded lines like:
+ // BODY;ENCODING=QUOTED-PRINTABLE:=
+ // another=20line=
+ // last=20line
+ while (preg_match_all('/^([^:]+;\s*(ENCODING=)?QUOTED-PRINTABLE(.*=\r?\n)+(.*[^=])?\r?\n)/mU', $vCal, $matches)) {
+ foreach ($matches[1] as $s) {
+ $r = preg_replace('/=\r?\n/', '', $s);
+ $vCal = str_replace($s, $r, $vCal);
+ }
+ }
+
+ // Unfold any folded lines.
+ if ($this->isOldFormat()) {
+ $vCal = preg_replace('/[\r\n]+([ \t])/', '$1', $vCal);
+ } else {
+ $vCal = preg_replace('/[\r\n]+[ \t]/', '', $vCal);
+ }
+
+ // Parse the remaining attributes.
+ if (preg_match_all('/^((?:[^":]+|(?:"[^"]*")+)*):([^\r\n]*)\r?$/m', $vCal, $matches)) {
+ foreach ($matches[0] as $attribute) {
+ preg_match('/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts);
+ $tag = trim(String::upper($parts[1]));
+ $value = $parts[4];
+ $params = array();
+
+ // Parse parameters.
+ if (!empty($parts[2])) {
+ preg_match_all('/;(([^;=]*)(=("[^"]*"|[^;]*))?)/', $parts[2], $param_parts);
+ foreach ($param_parts[2] as $key => $paramName) {
+ $paramName = String::upper($paramName);
+ $paramValue = $param_parts[4][$key];
+ if ($paramName == 'TYPE') {
+ $paramValue = preg_split('/(?<!\\\\),/', $paramValue);
+ if (count($paramValue) == 1) {
+ $paramValue = $paramValue[0];
+ }
+ }
+ if (is_string($paramValue)) {
+ if (preg_match('/"([^"]*)"/', $paramValue, $parts)) {
+ $paramValue = $parts[1];
+ }
+ } else {
+ foreach ($paramValue as $k => $tmp) {
+ if (preg_match('/"([^"]*)"/', $tmp, $parts)) {
+ $paramValue[$k] = $parts[1];
+ }
+ }
+ }
+ $params[$paramName] = $paramValue;
+ }
+ }
+
+ // Charset and encoding handling.
+ if ((isset($params['ENCODING']) &&
+ String::upper($params['ENCODING']) == 'QUOTED-PRINTABLE') ||
+ isset($params['QUOTED-PRINTABLE'])) {
+
+ $value = quoted_printable_decode($value);
+ if (isset($params['CHARSET'])) {
+ $value = String::convertCharset($value, $params['CHARSET']);
+ } else {
+ $value = String::convertCharset($value, empty($charset) ? ($this->isOldFormat() ? 'iso-8859-1' : 'utf-8') : $charset);
+ }
+ } elseif (isset($params['CHARSET'])) {
+ $value = String::convertCharset($value, $params['CHARSET']);
+ } else {
+ // As per RFC 2279, assume UTF8 if we don't have an
+ // explicit charset parameter.
+ $value = String::convertCharset($value, empty($charset) ? ($this->isOldFormat() ? 'iso-8859-1' : 'utf-8') : $charset);
+ }
+
+ // Get timezone info for date fields from $params.
+ $tzid = isset($params['TZID']) ? trim($params['TZID'], '\"') : false;
+
+ switch ($tag) {
+ // Date fields.
+ case 'COMPLETED':
+ case 'CREATED':
+ case 'LAST-MODIFIED':
+ case 'X-MOZ-LASTACK':
+ case 'X-MOZ-SNOOZE-TIME':
+ $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
+ break;
+
+ case 'BDAY':
+ case 'X-SYNCJE-ANNIVERSARY':
+ case 'X-ANNIVERSARY':
+ $this->setAttribute($tag, $this->_parseDate($value), $params);
+ break;
+
+ case 'DTEND':
+ case 'DTSTART':
+ case 'DTSTAMP':
+ case 'DUE':
+ case 'AALARM':
+ case 'RECURRENCE-ID':
+ // types like AALARM may contain additional data after a ;
+ // ignore these.
+ $ts = explode(';', $value);
+ if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') {
+ $this->setAttribute($tag, $this->_parseDate($ts[0]), $params);
+ } else {
+ $this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params);
+ }
+ break;
+
+ case 'TRIGGER':
+ if (isset($params['VALUE']) &&
+ $params['VALUE'] == 'DATE-TIME') {
+ $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
+ } else {
+ $this->setAttribute($tag, $this->_parseDuration($value), $params);
+ }
+ break;
+
+ // Comma seperated dates.
+ case 'EXDATE':
+ case 'RDATE':
+ if (!strlen($value)) {
+ break;
+ }
+ $dates = array();
+ $separator = $this->isOldFormat() ? ';' : ',';
+ preg_match_all('/' . $separator . '([^' . $separator . ']*)/', $separator . $value, $values);
+
+ foreach ($values[1] as $value) {
+ $dates[] = $this->_parseDate($value);
+ }
+ $this->setAttribute($tag, isset($dates[0]) ? $dates[0] : null, $params, true, $dates);
+ break;
+
+ // Duration fields.
+ case 'DURATION':
+ $this->setAttribute($tag, $this->_parseDuration($value), $params);
+ break;
+
+ // Period of time fields.
+ case 'FREEBUSY':
+ $periods = array();
+ preg_match_all('/,([^,]*)/', ',' . $value, $values);
+ foreach ($values[1] as $value) {
+ $periods[] = $this->_parsePeriod($value);
+ }
+
+ $this->setAttribute($tag, isset($periods[0]) ? $periods[0] : null, $params, true, $periods);
+ break;
+
+ // UTC offset fields.
+ case 'TZOFFSETFROM':
+ case 'TZOFFSETTO':
+ $this->setAttribute($tag, $this->_parseUtcOffset($value), $params);
+ break;
+
+ // Integer fields.
+ case 'PERCENT-COMPLETE':
+ case 'PRIORITY':
+ case 'REPEAT':
+ case 'SEQUENCE':
+ $this->setAttribute($tag, intval($value), $params);
+ break;
+
+ // Geo fields.
+ case 'GEO':
+ if ($this->isOldFormat()) {
+ $floats = explode(',', $value);
+ $value = array('latitude' => floatval($floats[1]),
+ 'longitude' => floatval($floats[0]));
+ } else {
+ $floats = explode(';', $value);
+ $value = array('latitude' => floatval($floats[0]),
+ 'longitude' => floatval($floats[1]));
+ }
+ $this->setAttribute($tag, $value, $params);
+ break;
+
+ // Recursion fields.
+ case 'EXRULE':
+ case 'RRULE':
+ $this->setAttribute($tag, trim($value), $params);
+ break;
+
+ // ADR, ORG and N are lists seperated by unescaped semicolons
+ // with a specific number of slots.
+ case 'ADR':
+ case 'N':
+ case 'ORG':
+ $value = trim($value);
+ // As of rfc 2426 2.4.2 semicolon, comma, and colon must
+ // be escaped (comma is unescaped after splitting below).
+ $value = str_replace(array('\\n', '\\N', '\\;', '\\:'),
+ array($this->_newline, $this->_newline, ';', ':'),
+ $value);
+
+ // Split by unescaped semicolons:
+ $values = preg_split('/(?<!\\\\);/', $value);
+ $value = str_replace('\\;', ';', $value);
+ $values = str_replace('\\;', ';', $values);
+ $this->setAttribute($tag, trim($value), $params, true, $values);
+ break;
+
+ // String fields.
+ default:
+ if ($this->isOldFormat()) {
+ // vCalendar 1.0 and vCard 2.1 only escape semicolons
+ // and use unescaped semicolons to create lists.
+ $value = trim($value);
+ // Split by unescaped semicolons:
+ $values = preg_split('/(?<!\\\\);/', $value);
+ $value = str_replace('\\;', ';', $value);
+ $values = str_replace('\\;', ';', $values);
+ $this->setAttribute($tag, trim($value), $params, true, $values);
+ } else {
+ $value = trim($value);
+ // As of rfc 2426 2.4.2 semicolon, comma, and colon
+ // must be escaped (comma is unescaped after splitting
+ // below).
+ $value = str_replace(array('\\n', '\\N', '\\;', '\\:', '\\\\'),
+ array($this->_newline, $this->_newline, ';', ':', '\\'),
+ $value);
+
+ // Split by unescaped commas.
+ $values = preg_split('/(?<!\\\\),/', $value);
+ $value = str_replace('\\,', ',', $value);
+ $values = str_replace('\\,', ',', $values);
+
+ $this->setAttribute($tag, trim($value), $params, true, $values);
+ }
+ break;
+ }
+ }
+ }
+
+ // Process all components.
+ if ($components) {
+ // vTimezone components are processed first. They are
+ // needed to process vEvents that may use a TZID.
+ foreach ($components[0] as $key => $data) {
+ $type = trim($components[1][$key]);
+ if ($type != 'VTIMEZONE') {
+ continue;
+ }
+ $component = &Horde_iCalendar::newComponent($type, $this);
+ if ($component === false) {
+ return PEAR::raiseError("Unable to create object for type $type");
+ }
+ $component->parsevCalendar($data, $type, $charset);
+
+ $this->addComponent($component);
+ }
+
+ // Now process the non-vTimezone components.
+ foreach ($components[0] as $key => $data) {
+ $type = trim($components[1][$key]);
+ if ($type == 'VTIMEZONE') {
+ continue;
+ }
+ $component = &Horde_iCalendar::newComponent($type, $this);
+ if ($component === false) {
+ return PEAR::raiseError("Unable to create object for type $type");
+ }
+ $component->parsevCalendar($data, $type, $charset);
+
+ $this->addComponent($component);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Export this component in vCal format.
+ *
+ * @param string $base The type of the base object.
+ *
+ * @return string vCal format data.
+ */
+ function _exportvData($base = 'VCALENDAR')
+ {
+ $result = 'BEGIN:' . String::upper($base) . $this->_newline;
+
+ // VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR,
+ // as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445
+ if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' &&
+ $base !== 'VJOURNAL' && $base !== 'VFREEBUSY') {
+ // Ensure that version is the first attribute.
+ $result .= 'VERSION:' . $this->_version . $this->_newline;
+ }
+ foreach ($this->_attributes as $attribute) {
+ $name = $attribute['name'];
+ if ($name == 'VERSION') {
+ // Already done.
+ continue;
+ }
+
+ $params_str = '';
+ $params = $attribute['params'];
+ if ($params) {
+ foreach ($params as $param_name => $param_value) {
+ /* Skip CHARSET for iCalendar 2.0 data, not allowed. */
+ if ($param_name == 'CHARSET' && !$this->isOldFormat()) {
+ continue;
+ }
+ /* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */
+ if ($this->isOldFormat() &&
+ $param_name == 'VALUE' && $param_value == 'DATE') {
+ continue;
+ }
+
+ if ($param_value === null) {
+ $params_str .= ";$param_name";
+ } else {
+ $len = strlen($param_value);
+ $safe_value = '';
+ $quote = false;
+ for ($i = 0; $i < $len; ++$i) {
+ $ord = ord($param_value[$i]);
+ // Accept only valid characters.
+ if ($ord == 9 || $ord == 32 || $ord == 33 ||
+ ($ord >= 35 && $ord <= 126) ||
+ $ord >= 128) {
+ $safe_value .= $param_value[$i];
+ // Characters above 128 do not need to be
+ // quoted as per RFC2445 but Outlook requires
+ // this.
+ if ($ord == 44 || $ord == 58 || $ord == 59 ||
+ $ord >= 128) {
+ $quote = true;
+ }
+ }
+ }
+ if ($quote) {
+ $safe_value = '"' . $safe_value . '"';
+ }
+ $params_str .= ";$param_name=$safe_value";
+ }
+ }
+ }
+
+ $value = $attribute['value'];
+ switch ($name) {
+ // Date fields.
+ case 'COMPLETED':
+ case 'CREATED':
+ case 'DCREATED':
+ case 'LAST-MODIFIED':
+ case 'X-MOZ-LASTACK':
+ case 'X-MOZ-SNOOZE-TIME':
+ $value = $this->_exportDateTime($value);
+ break;
+
+ case 'DTEND':
+ case 'DTSTART':
+ case 'DTSTAMP':
+ case 'DUE':
+ case 'AALARM':
+ case 'RECURRENCE-ID':
+ if (isset($params['VALUE'])) {
+ if ($params['VALUE'] == 'DATE') {
+ // VCALENDAR 1.0 uses T000000 - T235959 for all day events:
+ if ($this->isOldFormat() && $name == 'DTEND') {
+ $d = new Horde_Date($value);
+ $value = new Horde_Date(array(
+ 'year' => $d->year,
+ 'month' => $d->month,
+ 'mday' => $d->mday - 1));
+ $value->correct();
+ $value = $this->_exportDate($value, '235959');
+ } else {
+ $value = $this->_exportDate($value, '000000');
+ }
+ } else {
+ $value = $this->_exportDateTime($value);
+ }
+ } else {
+ $value = $this->_exportDateTime($value);
+ }
+ break;
+
+ // Comma seperated dates.
+ case 'EXDATE':
+ case 'RDATE':
+ $dates = array();
+ foreach ($value as $date) {
+ if (isset($params['VALUE'])) {
+ if ($params['VALUE'] == 'DATE') {
+ $dates[] = $this->_exportDate($date, '000000');
+ } elseif ($params['VALUE'] == 'PERIOD') {
+ $dates[] = $this->_exportPeriod($date);
+ } else {
+ $dates[] = $this->_exportDateTime($date);
+ }
+ } else {
+ $dates[] = $this->_exportDateTime($date);
+ }
+ }
+ $value = implode($this->isOldFormat() ? ';' : ',', $dates);
+ break;
+
+ case 'TRIGGER':
+ if (isset($params['VALUE'])) {
+ if ($params['VALUE'] == 'DATE-TIME') {
+ $value = $this->_exportDateTime($value);
+ } elseif ($params['VALUE'] == 'DURATION') {
+ $value = $this->_exportDuration($value);
+ }
+ } else {
+ $value = $this->_exportDuration($value);
+ }
+ break;
+
+ // Duration fields.
+ case 'DURATION':
+ $value = $this->_exportDuration($value);
+ break;
+
+ // Period of time fields.
+ case 'FREEBUSY':
+ $value_str = '';
+ foreach ($value as $period) {
+ $value_str .= empty($value_str) ? '' : ',';
+ $value_str .= $this->_exportPeriod($period);
+ }
+ $value = $value_str;
+ break;
+
+ // UTC offset fields.
+ case 'TZOFFSETFROM':
+ case 'TZOFFSETTO':
+ $value = $this->_exportUtcOffset($value);
+ break;
+
+ // Integer fields.
+ case 'PERCENT-COMPLETE':
+ case 'PRIORITY':
+ case 'REPEAT':
+ case 'SEQUENCE':
+ $value = "$value";
+ break;
+
+ // Geo fields.
+ case 'GEO':
+ if ($this->isOldFormat()) {
+ $value = $value['longitude'] . ',' . $value['latitude'];
+ } else {
+ $value = $value['latitude'] . ';' . $value['longitude'];
+ }
+ break;
+
+ // Recurrence fields.
+ case 'EXRULE':
+ case 'RRULE':
+ break;
+
+ default:
+ if ($this->isOldFormat()) {
+ if (is_array($attribute['values']) &&
+ count($attribute['values']) > 1) {
+ $values = $attribute['values'];
+ if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
+ $glue = ';';
+ } else {
+ $glue = ',';
+ }
+ $values = str_replace(';', '\\;', $values);
+ $value = implode($glue, $values);
+ } else {
+ /* vcard 2.1 and vcalendar 1.0 escape only
+ * semicolons */
+ $value = str_replace(';', '\\;', $value);
+ }
+ // Text containing newlines or ASCII >= 127 must be BASE64
+ // or QUOTED-PRINTABLE encoded. Currently we use
+ // QUOTED-PRINTABLE as default.
+ if (preg_match("/[^\x20-\x7F]/", $value) &&
+ empty($params['ENCODING'])) {
+ $params['ENCODING'] = 'QUOTED-PRINTABLE';
+ $params_str .= ';ENCODING=QUOTED-PRINTABLE';
+ // Add CHARSET as well. At least the synthesis client
+ // gets confused otherwise
+ if (empty($params['CHARSET'])) {
+ $params['CHARSET'] = 'UTF-8';
+ $params_str .= ';CHARSET=' . $params['CHARSET'];
+ }
+ }
+ } else {
+ if (is_array($attribute['values']) &&
+ count($attribute['values'])) {
+ $values = $attribute['values'];
+ if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
+ $glue = ';';
+ } else {
+ $glue = ',';
+ }
+ // As of rfc 2426 2.5 semicolon and comma must be
+ // escaped.
+ $values = str_replace(array('\\', ';', ','),
+ array('\\\\', '\\;', '\\,'),
+ $values);
+ $value = implode($glue, $values);
+ } else {
+ // As of rfc 2426 2.5 semicolon and comma must be
+ // escaped.
+ $value = str_replace(array('\\', ';', ','),
+ array('\\\\', '\\;', '\\,'),
+ $value);
+ }
+ $value = preg_replace('/\r?\n/', '\n', $value);
+ }
+ break;
+ }
+
+ $value = str_replace("\r", '', $value);
+ if (!empty($params['ENCODING']) &&
+ $params['ENCODING'] == 'QUOTED-PRINTABLE' &&
+ strlen(trim($value))) {
+ $result .= $name . $params_str . ':'
+ . str_replace('=0A', '=0D=0A',
+ $this->_quotedPrintableEncode($value))
+ . $this->_newline;
+ } else {
+ $attr_string = $name . $params_str . ':' . $value;
+ if (!$this->isOldFormat()) {
+ $attr_string = String::wordwrap($attr_string, 75, $this->_newline . ' ',
+ true, 'utf-8', true);
+ }
+ $result .= $attr_string . $this->_newline;
+ }
+ }
+
+ foreach ($this->_components as $component) {
+ $result .= $component->exportvCalendar();
+ }
+
+ return $result . 'END:' . $base . $this->_newline;
+ }
+
+ /**
+ * Parse a UTC Offset field.
+ */
+ function _parseUtcOffset($text)
+ {
+ $offset = array();
+ if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) {
+ $offset['ahead'] = (bool)($timeParts[1] == '+');
+ $offset['hour'] = intval($timeParts[2]);
+ $offset['minute'] = intval($timeParts[3]);
+ if (isset($timeParts[4])) {
+ $offset['second'] = intval($timeParts[4]);
+ }
+ return $offset;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Export a UTC Offset field.
+ */
+ function _exportUtcOffset($value)
+ {
+ $offset = $value['ahead'] ? '+' : '-';
+ $offset .= sprintf('%02d%02d',
+ $value['hour'], $value['minute']);
+ if (isset($value['second'])) {
+ $offset .= sprintf('%02d', $value['second']);
+ }
+
+ return $offset;
+ }
+
+ /**
+ * Parse a Time Period field.
+ */
+ function _parsePeriod($text)
+ {
+ $periodParts = explode('/', $text);
+
+ $start = $this->_parseDateTime($periodParts[0]);
+
+ if ($duration = $this->_parseDuration($periodParts[1])) {
+ return array('start' => $start, 'duration' => $duration);
+ } elseif ($end = $this->_parseDateTime($periodParts[1])) {
+ return array('start' => $start, 'end' => $end);
+ }
+ }
+
+ /**
+ * Export a Time Period field.
+ */
+ function _exportPeriod($value)
+ {
+ $period = $this->_exportDateTime($value['start']);
+ $period .= '/';
+ if (isset($value['duration'])) {
+ $period .= $this->_exportDuration($value['duration']);
+ } else {
+ $period .= $this->_exportDateTime($value['end']);
+ }
+ return $period;
+ }
+
+ /**
+ * Grok the TZID and return an offset in seconds from UTC for this
+ * date and time.
+ */
+ function _parseTZID($date, $time, $tzid)
+ {
+ $vtimezone = $this->_container->findComponentByAttribute('vtimezone', 'TZID', $tzid);
+ if (!$vtimezone) {
+ return false;
+ }
+
+ $change_times = array();
+ foreach ($vtimezone->getComponents() as $o) {
+ $t = $vtimezone->parseChild($o, $date['year']);
+ if ($t !== false) {
+ $change_times[] = $t;
+ }
+ }
+
+ if (!$change_times) {
+ return false;
+ }
+
+ sort($change_times);
+
+ // Time is arbitrarily based on UTC for comparison.
+ $t = @gmmktime($time['hour'], $time['minute'], $time['second'],
+ $date['month'], $date['mday'], $date['year']);
+
+ if ($t < $change_times[0]['time']) {
+ return $change_times[0]['from'];
+ }
+
+ for ($i = 0, $n = count($change_times); $i < $n - 1; $i++) {
+ if (($t >= $change_times[$i]['time']) &&
+ ($t < $change_times[$i + 1]['time'])) {
+ return $change_times[$i]['to'];
+ }
+ }
+
+ if ($t >= $change_times[$n - 1]['time']) {
+ return $change_times[$n - 1]['to'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses a DateTime field and returns a unix timestamp. If the
+ * field cannot be parsed then the original text is returned
+ * unmodified.
+ *
+ * @todo This function should be moved to Horde_Date and made public.
+ */
+ function _parseDateTime($text, $tzid = false)
+ {
+ $dateParts = explode('T', $text);
+ if (count($dateParts) != 2 && !empty($text)) {
+ // Not a datetime field but may be just a date field.
+ if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
+ // Or not
+ return $text;
+ }
+ $newtext = $text.'T000000';
+ $dateParts = explode('T', $newtext);
+ }
+
+ if (!$date = Horde_iCalendar::_parseDate($dateParts[0])) {
+ return $text;
+ }
+ if (!$time = Horde_iCalendar::_parseTime($dateParts[1])) {
+ return $text;
+ }
+
+ // Get timezone info for date fields from $tzid and container.
+ $tzoffset = ($time['zone'] == 'Local' && $tzid && is_a($this->_container, 'Horde_iCalendar'))
+ ? $this->_parseTZID($date, $time, $tzid) : false;
+ if ($time['zone'] == 'UTC' || $tzoffset !== false) {
+ $result = @gmmktime($time['hour'], $time['minute'], $time['second'],
+ $date['month'], $date['mday'], $date['year']);
+ if ($tzoffset) {
+ $result -= $tzoffset;
+ }
+ } else {
+ // We don't know the timezone so assume local timezone.
+ // FIXME: shouldn't this be based on the user's timezone
+ // preference rather than the server's timezone?
+ $result = @mktime($time['hour'], $time['minute'], $time['second'],
+ $date['month'], $date['mday'], $date['year']);
+ }
+
+ return ($result !== false) ? $result : $text;
+ }
+
+ /**
+ * Export a DateTime field.
+ */
+ function _exportDateTime($value)
+ {
+ $temp = array();
+ if (!is_object($value) && !is_array($value)) {
+ $tz = date('O', $value);
+ $TZOffset = (3600 * substr($tz, 0, 3)) + (60 * substr($tz, 3, 2));
+ $value -= $TZOffset;
+
+ $temp['zone'] = 'UTC';
+ list($temp['year'], $temp['month'], $temp['mday'], $temp['hour'], $temp['minute'], $temp['second']) = explode('-', date('Y-n-j-G-i-s', $value));
+ } else {
+ $dateOb = new Horde_Date($value);
+ return Horde_iCalendar::_exportDateTime($dateOb->timestamp());
+ }
+
+ return Horde_iCalendar::_exportDate($temp) . 'T' . Horde_iCalendar::_exportTime($temp);
+ }
+
+ /**
+ * Parses a Time field.
+ *
+ * @static
+ */
+ function _parseTime($text)
+ {
+ if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) {
+ $time['hour'] = intval($timeParts[1]);
+ $time['minute'] = intval($timeParts[2]);
+ $time['second'] = intval($timeParts[3]);
+ if (isset($timeParts[4])) {
+ $time['zone'] = 'UTC';
+ } else {
+ $time['zone'] = 'Local';
+ }
+ return $time;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Exports a Time field.
+ */
+ function _exportTime($value)
+ {
+ $time = sprintf('%02d%02d%02d',
+ $value['hour'], $value['minute'], $value['second']);
+ if ($value['zone'] == 'UTC') {
+ $time .= 'Z';
+ }
+ return $time;
+ }
+
+ /**
+ * Parses a Date field.
+ *
+ * @static
+ */
+ function _parseDate($text)
+ {
+ $parts = explode('T', $text);
+ if (count($parts) == 2) {
+ $text = $parts[0];
+ }
+
+ if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
+ return false;
+ }
+
+ return array('year' => $match[1],
+ 'month' => $match[2],
+ 'mday' => $match[3]);
+ }
+
+ /**
+ * Exports a date field.
+ *
+ * @param object|array $value Date object or hash.
+ * @param string $autoconvert If set, use this as time part to export the
+ * date as datetime when exporting to Vcalendar
+ * 1.0. Examples: '000000' or '235959'
+ */
+ function _exportDate($value, $autoconvert = false)
+ {
+ if (is_object($value)) {
+ $value = array('year' => $value->year, 'month' => $value->month, 'mday' => $value->mday);
+ }
+ if ($autoconvert !== false && $this->isOldFormat()) {
+ return sprintf('%04d%02d%02dT%s', $value['year'], $value['month'], $value['mday'], $autoconvert);
+ } else {
+ return sprintf('%04d%02d%02d', $value['year'], $value['month'], $value['mday']);
+ }
+ }
+
+ /**
+ * Parse a Duration Value field.
+ */
+ function _parseDuration($text)
+ {
+ if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) {
+ // Weeks.
+ $duration = 7 * 86400 * intval($durvalue[3]);
+
+ if (count($durvalue) > 4) {
+ // Days.
+ $duration += 86400 * intval($durvalue[4]);
+ }
+ if (count($durvalue) > 5) {
+ // Hours.
+ $duration += 3600 * intval($durvalue[7]);
+
+ // Mins.
+ if (isset($durvalue[8])) {
+ $duration += 60 * intval($durvalue[8]);
+ }
+
+ // Secs.
+ if (isset($durvalue[9])) {
+ $duration += intval($durvalue[9]);
+ }
+ }
+
+ // Sign.
+ if ($durvalue[1] == "-") {
+ $duration *= -1;
+ }
+
+ return $duration;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Export a duration value.
+ */
+ function _exportDuration($value)
+ {
+ $duration = '';
+ if ($value < 0) {
+ $value *= -1;
+ $duration .= '-';
+ }
+ $duration .= 'P';
+
+ $weeks = floor($value / (7 * 86400));
+ $value = $value % (7 * 86400);
+ if ($weeks) {
+ $duration .= $weeks . 'W';
+ }
+
+ $days = floor($value / (86400));
+ $value = $value % (86400);
+ if ($days) {
+ $duration .= $days . 'D';
+ }
+
+ if ($value) {
+ $duration .= 'T';
+
+ $hours = floor($value / 3600);
+ $value = $value % 3600;
+ if ($hours) {
+ $duration .= $hours . 'H';
+ }
+
+ $mins = floor($value / 60);
+ $value = $value % 60;
+ if ($mins) {
+ $duration .= $mins . 'M';
+ }
+
+ if ($value) {
+ $duration .= $value . 'S';
+ }
+ }
+
+ return $duration;
+ }
+
+ /**
+ * Converts an 8bit string to a quoted-printable string according to RFC
+ * 2045, section 6.7.
+ *
+ * imap_8bit() does not apply all necessary rules.
+ *
+ * @param string $input The string to be encoded.
+ *
+ * @return string The quoted-printable encoded string.
+ */
+ function _quotedPrintableEncode($input = '')
+ {
+ $output = $line = '';
+ $len = strlen($input);
+
+ for ($i = 0; $i < $len; ++$i) {
+ $ord = ord($input[$i]);
+ // Encode non-printable characters (rule 2).
+ if ($ord == 9 ||
+ ($ord >= 32 && $ord <= 60) ||
+ ($ord >= 62 && $ord <= 126)) {
+ $chunk = $input[$i];
+ } else {
+ // Quoted printable encoding (rule 1).
+ $chunk = '=' . String::upper(sprintf('%02X', $ord));
+ }
+ $line .= $chunk;
+ // Wrap long lines (rule 5)
+ if (strlen($line) + 1 > 76) {
+ $line = String::wordwrap($line, 75, "=\r\n", true, 'us-ascii', true);
+ $newline = strrchr($line, "\r\n");
+ if ($newline !== false) {
+ $output .= substr($line, 0, -strlen($newline) + 2);
+ $line = substr($newline, 2);
+ } else {
+ $output .= $line;
+ }
+ continue;
+ }
+ // Wrap at line breaks for better readability (rule 4).
+ if (substr($line, -3) == '=0A') {
+ $output .= $line . "=\r\n";
+ $line = '';
+ }
+ }
+ $output .= $line;
+
+ // Trailing whitespace must be encoded (rule 3).
+ $lastpos = strlen($output) - 1;
+ if ($output[$lastpos] == chr(9) ||
+ $output[$lastpos] == chr(32)) {
+ $output[$lastpos] = '=';
+ $output .= String::upper(sprintf('%02X', ord($output[$lastpos])));
+ }
+
+ return $output;
+ }
+
+}
+
+
+
+/**
+ * Class representing vAlarms.
+ *
+ * $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8.10.9 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_valarm extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'vAlarm';
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('VALARM');
+ }
+
+}
+
+/**
+ * Class representing vEvents.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31.10.16 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vevent extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'vEvent';
+ }
+
+ function exportvCalendar()
+ {
+ // Default values.
+ $requiredAttributes = array();
+ $requiredAttributes['DTSTAMP'] = time();
+ $requiredAttributes['UID'] = $this->_exportDateTime(time())
+ . substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16)
+ . '@' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
+
+ $method = !empty($this->_container) ?
+ $this->_container->getAttribute('METHOD') : 'PUBLISH';
+
+ switch ($method) {
+ case 'PUBLISH':
+ $requiredAttributes['DTSTART'] = time();
+ $requiredAttributes['SUMMARY'] = '';
+ break;
+
+ case 'REQUEST':
+ $requiredAttributes['ATTENDEE'] = '';
+ $requiredAttributes['DTSTART'] = time();
+ $requiredAttributes['SUMMARY'] = '';
+ break;
+
+ case 'REPLY':
+ $requiredAttributes['ATTENDEE'] = '';
+ break;
+
+ case 'ADD':
+ $requiredAttributes['DTSTART'] = time();
+ $requiredAttributes['SEQUENCE'] = 1;
+ $requiredAttributes['SUMMARY'] = '';
+ break;
+
+ case 'CANCEL':
+ $requiredAttributes['ATTENDEE'] = '';
+ $requiredAttributes['SEQUENCE'] = 1;
+ break;
+
+ case 'REFRESH':
+ $requiredAttributes['ATTENDEE'] = '';
+ break;
+ }
+
+ foreach ($requiredAttributes as $name => $default_value) {
+ if (is_a($this->getAttribute($name), 'PEAR_Error')) {
+ $this->setAttribute($name, $default_value);
+ }
+ }
+
+ return parent::_exportvData('VEVENT');
+ }
+
+ /**
+ * Update the status of an attendee of an event.
+ *
+ * @param $email The email address of the attendee.
+ * @param $status The participant status to set.
+ * @param $fullname The full name of the participant to set.
+ */
+ function updateAttendee($email, $status, $fullname = '')
+ {
+ foreach ($this->_attributes as $key => $attribute) {
+ if ($attribute['name'] == 'ATTENDEE' &&
+ $attribute['value'] == 'mailto:' . $email) {
+ $this->_attributes[$key]['params']['PARTSTAT'] = $status;
+ if (!empty($fullname)) {
+ $this->_attributes[$key]['params']['CN'] = $fullname;
+ }
+ unset($this->_attributes[$key]['params']['RSVP']);
+ return;
+ }
+ }
+ $params = array('PARTSTAT' => $status);
+ if (!empty($fullname)) {
+ $params['CN'] = $fullname;
+ }
+ $this->setAttribute('ATTENDEE', 'mailto:' . $email, $params);
+ }
+
+ /**
+ * Return the organizer display name or email.
+ *
+ * @return string The organizer name to display for this event.
+ */
+ function organizerName()
+ {
+ $organizer = $this->getAttribute('ORGANIZER', true);
+ if (is_a($organizer, 'PEAR_Error')) {
+ return _("An unknown person");
+ }
+
+ if (isset($organizer[0]['CN'])) {
+ return $organizer[0]['CN'];
+ }
+
+ $organizer = parse_url($this->getAttribute('ORGANIZER'));
+
+ return $organizer['path'];
+ }
+
+ /**
+ * Update this event with details from another event.
+ *
+ * @param Horde_iCalendar_vEvent $vevent The vEvent with latest details.
+ */
+ function updateFromvEvent($vevent)
+ {
+ $newAttributes = $vevent->getAllAttributes();
+ foreach ($newAttributes as $newAttribute) {
+ $currentValue = $this->getAttribute($newAttribute['name']);
+ if (is_a($currentValue, 'PEAR_error')) {
+ // Already exists so just add it.
+ $this->setAttribute($newAttribute['name'],
+ $newAttribute['value'],
+ $newAttribute['params']);
+ } else {
+ // Already exists so locate and modify.
+ $found = false;
+
+ // Try matching the attribte name and value incase
+ // only the params changed (eg attendee updating
+ // status).
+ foreach ($this->_attributes as $id => $attr) {
+ if ($attr['name'] == $newAttribute['name'] &&
+ $attr['value'] == $newAttribute['value']) {
+ // merge the params
+ foreach ($newAttribute['params'] as $param_id => $param_name) {
+ $this->_attributes[$id]['params'][$param_id] = $param_name;
+ }
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ // Else match the first attribute with the same
+ // name (eg changing start time).
+ foreach ($this->_attributes as $id => $attr) {
+ if ($attr['name'] == $newAttribute['name']) {
+ $this->_attributes[$id]['value'] = $newAttribute['value'];
+ // Merge the params.
+ foreach ($newAttribute['params'] as $param_id => $param_name) {
+ $this->_attributes[$id]['params'][$param_id] = $param_name;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update just the attendess of event with details from another
+ * event.
+ *
+ * @param Horde_iCalendar_vEvent $vevent The vEvent with latest details
+ */
+ function updateAttendeesFromvEvent($vevent)
+ {
+ $newAttributes = $vevent->getAllAttributes();
+ foreach ($newAttributes as $newAttribute) {
+ if ($newAttribute['name'] != 'ATTENDEE') {
+ continue;
+ }
+ $currentValue = $this->getAttribute($newAttribute['name']);
+ if (is_a($currentValue, 'PEAR_error')) {
+ // Already exists so just add it.
+ $this->setAttribute($newAttribute['name'],
+ $newAttribute['value'],
+ $newAttribute['params']);
+ } else {
+ // Already exists so locate and modify.
+ $found = false;
+ // Try matching the attribte name and value incase
+ // only the params changed (eg attendee updating
+ // status).
+ foreach ($this->_attributes as $id => $attr) {
+ if ($attr['name'] == $newAttribute['name'] &&
+ $attr['value'] == $newAttribute['value']) {
+ // Merge the params.
+ foreach ($newAttribute['params'] as $param_id => $param_name) {
+ $this->_attributes[$id]['params'][$param_id] = $param_name;
+ }
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ // Else match the first attribute with the same
+ // name (eg changing start time).
+ foreach ($this->_attributes as $id => $attr) {
+ if ($attr['name'] == $newAttribute['name']) {
+ $this->_attributes[$id]['value'] = $newAttribute['value'];
+ // Merge the params.
+ foreach ($newAttribute['params'] as $param_id => $param_name) {
+ $this->_attributes[$id]['params'][$param_id] = $param_name;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
+
+/**
+ * Class representing vFreebusy components.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vfreebusy.php,v 1.16.10.18 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @todo Don't use timestamps
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
+
+ var $_busyPeriods = array();
+ var $_extraParams = array();
+
+ /**
+ * Returns the type of this calendar component.
+ *
+ * @return string The type of this component.
+ */
+ function getType()
+ {
+ return 'vFreebusy';
+ }
+
+ /**
+ * Parses a string containing vFreebusy data.
+ *
+ * @param string $data The data to parse.
+ */
+ function parsevCalendar($data, $type = null, $charset = null)
+ {
+ parent::parsevCalendar($data, 'VFREEBUSY', $charset);
+
+ // Do something with all the busy periods.
+ foreach ($this->_attributes as $key => $attribute) {
+ if ($attribute['name'] != 'FREEBUSY') {
+ continue;
+ }
+ foreach ($attribute['values'] as $value) {
+ $params = isset($attribute['params'])
+ ? $attribute['params']
+ : array();
+ if (isset($value['duration'])) {
+ $this->addBusyPeriod('BUSY', $value['start'], null,
+ $value['duration'], $params);
+ } else {
+ $this->addBusyPeriod('BUSY', $value['start'],
+ $value['end'], null, $params);
+ }
+ }
+ unset($this->_attributes[$key]);
+ }
+ }
+
+ /**
+ * Returns the component exported as string.
+ *
+ * @return string The exported vFreeBusy information according to the
+ * iCalender format specification.
+ */
+ function exportvCalendar()
+ {
+ foreach ($this->_busyPeriods as $start => $end) {
+ $periods = array(array('start' => $start, 'end' => $end));
+ $this->setAttribute('FREEBUSY', $periods,
+ isset($this->_extraParams[$start])
+ ? $this->_extraParams[$start] : array());
+ }
+
+ $res = parent::_exportvData('VFREEBUSY');
+
+ foreach ($this->_attributes as $key => $attribute) {
+ if ($attribute['name'] == 'FREEBUSY') {
+ unset($this->_attributes[$key]);
+ }
+ }
+
+ return $res;
+ }
+
+ /**
+ * Returns a display name for this object.
+ *
+ * @return string A clear text name for displaying this object.
+ */
+ function getName()
+ {
+ $name = '';
+ $method = !empty($this->_container) ?
+ $this->_container->getAttribute('METHOD') : 'PUBLISH';
+
+ if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
+ $attr = 'ORGANIZER';
+ } elseif ($method == 'REPLY') {
+ $attr = 'ATTENDEE';
+ }
+
+ $name = $this->getAttribute($attr, true);
+ if (!is_a($name, 'PEAR_Error') && isset($name[0]['CN'])) {
+ return $name[0]['CN'];
+ }
+
+ $name = $this->getAttribute($attr);
+ if (is_a($name, 'PEAR_Error')) {
+ return '';
+ } else {
+ $name = parse_url($name);
+ return $name['path'];
+ }
+ }
+
+ /**
+ * Returns the email address for this object.
+ *
+ * @return string The email address of this object's owner.
+ */
+ function getEmail()
+ {
+ $name = '';
+ $method = !empty($this->_container)
+ ? $this->_container->getAttribute('METHOD') : 'PUBLISH';
+
+ if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
+ $attr = 'ORGANIZER';
+ } elseif ($method == 'REPLY') {
+ $attr = 'ATTENDEE';
+ }
+
+ $name = $this->getAttribute($attr);
+ if (is_a($name, 'PEAR_Error')) {
+ return '';
+ } else {
+ $name = parse_url($name);
+ return $name['path'];
+ }
+ }
+
+ /**
+ * Returns the busy periods.
+ *
+ * @return array All busy periods.
+ */
+ function getBusyPeriods()
+ {
+ return $this->_busyPeriods;
+ }
+
+ /**
+ * Returns any additional freebusy parameters.
+ *
+ * @return array Additional parameters of the freebusy periods.
+ */
+ function getExtraParams()
+ {
+ return $this->_extraParams;
+ }
+
+ /**
+ * Returns all the free periods of time in a given period.
+ *
+ * @param integer $startStamp The start timestamp.
+ * @param integer $endStamp The end timestamp.
+ *
+ * @return array A hash with free time periods, the start times as the
+ * keys and the end times as the values.
+ */
+ function getFreePeriods($startStamp, $endStamp)
+ {
+ $this->simplify();
+ $periods = array();
+
+ // Check that we have data for some part of this period.
+ if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) {
+ return $periods;
+ }
+
+ // Locate the first time in the requested period we have data for.
+ $nextstart = max($startStamp, $this->getStart());
+
+ // Check each busy period and add free periods in between.
+ foreach ($this->_busyPeriods as $start => $end) {
+ if ($start <= $endStamp && $end >= $nextstart) {
+ if ($nextstart <= $start) {
+ $periods[$nextstart] = min($start, $endStamp);
+ }
+ $nextstart = min($end, $endStamp);
+ }
+ }
+
+ // If we didn't read the end of the requested period but still have
+ // data then mark as free to the end of the period or available data.
+ if ($nextstart < $endStamp && $nextstart < $this->getEnd()) {
+ $periods[$nextstart] = min($this->getEnd(), $endStamp);
+ }
+
+ return $periods;
+ }
+
+ /**
+ * Adds a busy period to the info.
+ *
+ * This function may throw away data in case you add a period with a start
+ * date that already exists. The longer of the two periods will be chosen
+ * (and all information associated with the shorter one will be removed).
+ *
+ * @param string $type The type of the period. Either 'FREE' or
+ * 'BUSY'; only 'BUSY' supported at the moment.
+ * @param integer $start The start timestamp of the period.
+ * @param integer $end The end timestamp of the period.
+ * @param integer $duration The duration of the period. If specified, the
+ * $end parameter will be ignored.
+ * @param array $extra Additional parameters for this busy period.
+ */
+ function addBusyPeriod($type, $start, $end = null, $duration = null,
+ $extra = array())
+ {
+ if ($type == 'FREE') {
+ // Make sure this period is not marked as busy.
+ return false;
+ }
+
+ // Calculate the end time if duration was specified.
+ $tempEnd = is_null($duration) ? $end : $start + $duration;
+
+ // Make sure the period length is always positive.
+ $end = max($start, $tempEnd);
+ $start = min($start, $tempEnd);
+
+ if (isset($this->_busyPeriods[$start])) {
+ // Already a period starting at this time. Change the current
+ // period only if the new one is longer. This might be a problem
+ // if the callee assumes that there is no simplification going
+ // on. But since the periods are stored using the start time of
+ // the busy periods we have to throw away data here.
+ if ($end > $this->_busyPeriods[$start]) {
+ $this->_busyPeriods[$start] = $end;
+ $this->_extraParams[$start] = $extra;
+ }
+ } else {
+ // Add a new busy period.
+ $this->_busyPeriods[$start] = $end;
+ $this->_extraParams[$start] = $extra;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the timestamp of the start of the time period this free busy
+ * information covers.
+ *
+ * @return integer A timestamp.
+ */
+ function getStart()
+ {
+ if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) {
+ return $this->getAttribute('DTSTART');
+ } elseif (count($this->_busyPeriods)) {
+ return min(array_keys($this->_busyPeriods));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the timestamp of the end of the time period this free busy
+ * information covers.
+ *
+ * @return integer A timestamp.
+ */
+ function getEnd()
+ {
+ if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) {
+ return $this->getAttribute('DTEND');
+ } elseif (count($this->_busyPeriods)) {
+ return max(array_values($this->_busyPeriods));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Merges the busy periods of another Horde_iCalendar_vfreebusy object
+ * into this one.
+ *
+ * This might lead to simplification no matter what you specify for the
+ * "simplify" flag since periods with the same start date will lead to the
+ * shorter period being removed (see addBusyPeriod).
+ *
+ * @param Horde_iCalendar_vfreebusy $freebusy A freebusy object.
+ * @param boolean $simplify If true, simplify() will
+ * called after the merge.
+ */
+ function merge($freebusy, $simplify = true)
+ {
+ if (!is_a($freebusy, 'Horde_iCalendar_vfreebusy')) {
+ return false;
+ }
+
+ $extra = $freebusy->getExtraParams();
+ foreach ($freebusy->getBusyPeriods() as $start => $end) {
+ // This might simplify the busy periods without taking the
+ // "simplify" flag into account.
+ $this->addBusyPeriod('BUSY', $start, $end, null,
+ isset($extra[$start])
+ ? $extra[$start] : array());
+ }
+
+ $thisattr = $this->getAttribute('DTSTART');
+ $thatattr = $freebusy->getAttribute('DTSTART');
+ if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
+ $this->setAttribute('DTSTART', $thatattr, array(), false);
+ } elseif (!is_a($thatattr, 'PEAR_Error')) {
+ if ($thatattr < $thisattr) {
+ $this->setAttribute('DTSTART', $thatattr, array(), false);
+ }
+ }
+
+ $thisattr = $this->getAttribute('DTEND');
+ $thatattr = $freebusy->getAttribute('DTEND');
+ if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
+ $this->setAttribute('DTEND', $thatattr, array(), false);
+ } elseif (!is_a($thatattr, 'PEAR_Error')) {
+ if ($thatattr > $thisattr) {
+ $this->setAttribute('DTEND', $thatattr, array(), false);
+ }
+ }
+
+ if ($simplify) {
+ $this->simplify();
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes all overlaps and simplifies the busy periods array as much as
+ * possible.
+ */
+ function simplify()
+ {
+ $clean = false;
+ $busy = array($this->_busyPeriods, $this->_extraParams);
+ while (!$clean) {
+ $result = $this->_simplify($busy[0], $busy[1]);
+ $clean = $result === $busy;
+ $busy = $result;
+ }
+
+ ksort($result[1], SORT_NUMERIC);
+ $this->_extraParams = $result[1];
+
+ ksort($result[0], SORT_NUMERIC);
+ $this->_busyPeriods = $result[0];
+ }
+
+ function _simplify($busyPeriods, $extraParams = array())
+ {
+ $checked = array();
+ $checkedExtra = array();
+ $checkedEmpty = true;
+
+ foreach ($busyPeriods as $start => $end) {
+ if ($checkedEmpty) {
+ $checked[$start] = $end;
+ $checkedExtra[$start] = isset($extraParams[$start])
+ ? $extraParams[$start] : array();
+ $checkedEmpty = false;
+ } else {
+ $added = false;
+ foreach ($checked as $testStart => $testEnd) {
+ // Replace old period if the new period lies around the
+ // old period.
+ if ($start <= $testStart && $end >= $testEnd) {
+ // Remove old period entry.
+ unset($checked[$testStart]);
+ unset($checkedExtra[$testStart]);
+ // Add replacing entry.
+ $checked[$start] = $end;
+ $checkedExtra[$start] = isset($extraParams[$start])
+ ? $extraParams[$start] : array();
+ $added = true;
+ } elseif ($start >= $testStart && $end <= $testEnd) {
+ // The new period lies fully within the old
+ // period. Just forget about it.
+ $added = true;
+ } elseif (($end <= $testEnd && $end >= $testStart) ||
+ ($start >= $testStart && $start <= $testEnd)) {
+ // Now we are in trouble: Overlapping time periods. If
+ // we allow for additional parameters we cannot simply
+ // choose one of the two parameter sets. It's better
+ // to leave two separated time periods.
+ $extra = isset($extraParams[$start])
+ ? $extraParams[$start] : array();
+ $testExtra = isset($checkedExtra[$testStart])
+ ? $checkedExtra[$testStart] : array();
+ // Remove old period entry.
+ unset($checked[$testStart]);
+ unset($checkedExtra[$testStart]);
+ // We have two periods overlapping. Are their
+ // additional parameters the same or different?
+ $newStart = min($start, $testStart);
+ $newEnd = max($end, $testEnd);
+ if ($extra === $testExtra) {
+ // Both periods have the same information. So we
+ // can just merge.
+ $checked[$newStart] = $newEnd;
+ $checkedExtra[$newStart] = $extra;
+ } else {
+ // Extra parameters are different. Create one
+ // period at the beginning with the params of the
+ // first period and create a trailing period with
+ // the params of the second period. The break
+ // point will be the end of the first period.
+ $break = min($end, $testEnd);
+ $checked[$newStart] = $break;
+ $checkedExtra[$newStart] =
+ isset($extraParams[$newStart])
+ ? $extraParams[$newStart] : array();
+ $checked[$break] = $newEnd;
+ $highStart = max($start, $testStart);
+ $checkedExtra[$break] =
+ isset($extraParams[$highStart])
+ ? $extraParams[$highStart] : array();
+
+ // Ensure we also have the extra data in the
+ // extraParams.
+ $extraParams[$break] =
+ isset($extraParams[$highStart])
+ ? $extraParams[$highStart] : array();
+ }
+ $added = true;
+ }
+
+ if ($added) {
+ break;
+ }
+ }
+
+ if (!$added) {
+ $checked[$start] = $end;
+ $checkedExtra[$start] = isset($extraParams[$start])
+ ? $extraParams[$start] : array();
+ }
+ }
+ }
+
+ return array($checked, $checkedExtra);
+ }
+
+}
+
+/**
+ * Class representing vJournals.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8.10.9 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vjournal extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'vJournal';
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('VJOURNAL');
+ }
+
+}
+
+
+
+
+/**
+ * Class representing vNotes.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.3.10.10 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @author Karsten Fourmont <fourmont@gmx.de>
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vnote extends Horde_iCalendar {
+
+ function Horde_iCalendar_vnote($version = '1.1')
+ {
+ return parent::Horde_iCalendar($version);
+ }
+
+ function getType()
+ {
+ return 'vNote';
+ }
+
+ /**
+ * Unlike vevent and vtodo, a vnote is normally not enclosed in an
+ * iCalendar container. (BEGIN..END)
+ */
+ function exportvCalendar()
+ {
+ $requiredAttributes['BODY'] = '';
+ $requiredAttributes['VERSION'] = '1.1';
+
+ foreach ($requiredAttributes as $name => $default_value) {
+ if (is_a($this->getattribute($name), 'PEAR_Error')) {
+ $this->setAttribute($name, $default_value);
+ }
+ }
+
+ return $this->_exportvData('VNOTE');
+ }
+
+}
+
+/**
+ * Class representing vTimezones.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8.10.10 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vtimezone extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'vTimeZone';
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('VTIMEZONE');
+ }
+
+ /**
+ * Parse child components of the vTimezone component. Returns an
+ * array with the exact time of the time change as well as the
+ * 'from' and 'to' offsets around the change. Time is arbitrarily
+ * based on UTC for comparison.
+ */
+ function parseChild(&$child, $year)
+ {
+ // Make sure 'time' key is first for sort().
+ $result['time'] = 0;
+
+ $t = $child->getAttribute('TZOFFSETFROM');
+ if (is_a($t, 'PEAR_Error')) {
+ return false;
+ }
+ $result['from'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1);
+
+ $t = $child->getAttribute('TZOFFSETTO');
+ if (is_a($t, 'PEAR_Error')) {
+ return false;
+ }
+ $result['to'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1);
+
+ $switch_time = $child->getAttribute('DTSTART');
+ if (is_a($switch_time, 'PEAR_Error')) {
+ return false;
+ }
+
+ $rrules = $child->getAttribute('RRULE');
+ if (is_a($rrules, 'PEAR_Error')) {
+ if (!is_int($switch_time)) {
+ return false;
+ }
+ // Convert this timestamp from local time to UTC for
+ // comparison (All dates are compared as if they are UTC).
+ $t = getdate($switch_time);
+ $result['time'] = @gmmktime($t['hours'], $t['minutes'], $t['seconds'],
+ $t['mon'], $t['mday'], $t['year']);
+ return $result;
+ }
+
+ $rrules = explode(';', $rrules);
+ foreach ($rrules as $rrule) {
+ $t = explode('=', $rrule);
+ switch ($t[0]) {
+ case 'FREQ':
+ if ($t[1] != 'YEARLY') {
+ return false;
+ }
+ break;
+
+ case 'INTERVAL':
+ if ($t[1] != '1') {
+ return false;
+ }
+ break;
+
+ case 'BYMONTH':
+ $month = intval($t[1]);
+ break;
+
+ case 'BYDAY':
+ $len = strspn($t[1], '1234567890-+');
+ if ($len == 0) {
+ return false;
+ }
+ $weekday = substr($t[1], $len);
+ $weekdays = array(
+ 'SU' => 0,
+ 'MO' => 1,
+ 'TU' => 2,
+ 'WE' => 3,
+ 'TH' => 4,
+ 'FR' => 5,
+ 'SA' => 6
+ );
+ $weekday = $weekdays[$weekday];
+ $which = intval(substr($t[1], 0, $len));
+ break;
+
+ case 'UNTIL':
+ if (intval($year) > intval(substr($t[1], 0, 4))) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (empty($month) || !isset($weekday)) {
+ return false;
+ }
+
+ if (is_int($switch_time)) {
+ // Was stored as localtime.
+ $switch_time = strftime('%H:%M:%S', $switch_time);
+ $switch_time = explode(':', $switch_time);
+ } else {
+ $switch_time = explode('T', $switch_time);
+ if (count($switch_time) != 2) {
+ return false;
+ }
+ $switch_time[0] = substr($switch_time[1], 0, 2);
+ $switch_time[2] = substr($switch_time[1], 4, 2);
+ $switch_time[1] = substr($switch_time[1], 2, 2);
+ }
+
+ // Get the timestamp for the first day of $month.
+ $when = gmmktime($switch_time[0], $switch_time[1], $switch_time[2],
+ $month, 1, $year);
+ // Get the day of the week for the first day of $month.
+ $first_of_month_weekday = intval(gmstrftime('%w', $when));
+
+ // Go to the first $weekday before first day of $month.
+ if ($weekday >= $first_of_month_weekday) {
+ $weekday -= 7;
+ }
+ $when -= ($first_of_month_weekday - $weekday) * 60 * 60 * 24;
+
+ // If going backwards go to the first $weekday after last day
+ // of $month.
+ if ($which < 0) {
+ do {
+ $when += 60*60*24*7;
+ } while (intval(gmstrftime('%m', $when)) == $month);
+ }
+
+ // Calculate $weekday number $which.
+ $when += $which * 60 * 60 * 24 * 7;
+
+ $result['time'] = $when;
+
+ return $result;
+ }
+
+}
+
+/**
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_standard extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'standard';
+ }
+
+ function parsevCalendar($data)
+ {
+ parent::parsevCalendar($data, 'STANDARD');
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('STANDARD');
+ }
+
+}
+
+/**
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_daylight extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'daylight';
+ }
+
+ function parsevCalendar($data)
+ {
+ parent::parsevCalendar($data, 'DAYLIGHT');
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('DAYLIGHT');
+ }
+
+}
+
+/**
+ * Class representing vTodos.
+ *
+ * $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13.10.9 2009-01-06 15:23:53 jan Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Mike Cochrane <mike@graftonhall.co.nz>
+ * @since Horde 3.0
+ * @package Horde_iCalendar
+ */
+class Horde_iCalendar_vtodo extends Horde_iCalendar {
+
+ function getType()
+ {
+ return 'vTodo';
+ }
+
+ function exportvCalendar()
+ {
+ return parent::_exportvData('VTODO');
+ }
+
+ /**
+ * Convert this todo to an array of attributes.
+ *
+ * @return array Array containing the details of the todo in a hash
+ * as used by Horde applications.
+ */
+ function toArray()
+ {
+ $todo = array();
+
+ $name = $this->getAttribute('SUMMARY');
+ if (!is_array($name) && !is_a($name, 'PEAR_Error')) {
+ $todo['name'] = $name;
+ }
+ $desc = $this->getAttribute('DESCRIPTION');
+ if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) {
+ $todo['desc'] = $desc;
+ }
+
+ $priority = $this->getAttribute('PRIORITY');
+ if (!is_array($priority) && !is_a($priority, 'PEAR_Error')) {
+ $todo['priority'] = $priority;
+ }
+
+ $due = $this->getAttribute('DTSTAMP');
+ if (!is_array($due) && !is_a($due, 'PEAR_Error')) {
+ $todo['due'] = $due;
+ }
+
+ return $todo;
+ }
+
+ /**
+ * Set the attributes for this todo item from an array.
+ *
+ * @param array $todo Array containing the details of the todo in
+ * the same format that toArray() exports.
+ */
+ function fromArray($todo)
+ {
+ if (isset($todo['name'])) {
+ $this->setAttribute('SUMMARY', $todo['name']);
+ }
+ if (isset($todo['desc'])) {
+ $this->setAttribute('DESCRIPTION', $todo['desc']);
+ }
+
+ if (isset($todo['priority'])) {
+ $this->setAttribute('PRIORITY', $todo['priority']);
+ }
+
+ if (isset($todo['due'])) {
+ $this->setAttribute('DTSTAMP', $todo['due']);
+ }
+ }
+
+}
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index c99fab35..dd613724 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -1,461 +1,461 @@
<?php
/**
* iCalendar functions for the Calendar plugin
*
* @version @package_version@
* @author Lazlo Westerhof <hello@lazlo.me>
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Bogomil "Bogo" Shopov <shopov@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Class to parse and build vCalendar (iCalendar) files
*
* Uses the Horde:iCalendar class for parsing. To install:
* > pear channel-discover pear.horde.org
* > pear install horde/Horde_Icalendar
*
*/
class calendar_ical
{
const EOL = "\r\n";
private $rc;
private $cal;
public $method;
public $events = array();
function __construct($cal)
{
$this->cal = $cal;
$this->rc = $cal->rc;
}
/**
* Import events from iCalendar format
*
* @param string vCalendar input
* @param string Input charset (from envelope)
* @return array List of events extracted from the input
*/
public function import($vcal, $charset = RCMAIL_CHARSET)
{
$parser = $this->get_parser();
$parser->parsevCalendar($vcal, 'VCALENDAR', $charset);
$this->method = $parser->getAttributeDefault('METHOD', '');
$this->events = $seen = array();
if ($data = $parser->getComponents()) {
foreach ($data as $comp) {
if ($comp->getType() == 'vEvent') {
$event = $this->_to_rcube_format($comp);
if (!$seen[$event['uid']]++)
$this->events[] = $event;
}
}
}
return $this->events;
}
/**
* Read iCalendar events from a file
*
* @param string File path to read from
* @return array List of events extracted from the file
*/
public function import_from_file($filepath)
{
$this->events = $seen = array();
$fp = fopen($filepath, 'r');
// check file content first
$begin = fread($fp, 1024);
if (!preg_match('/BEGIN:VCALENDAR/i', $begin))
return $this->events;
$parser = $this->get_parser();
$buffer = '';
fseek($fp, 0);
while (($line = fgets($fp, 2048)) !== false) {
$buffer .= $line;
if (preg_match('/END:VEVENT/i', $line)) {
$parser->parsevCalendar($buffer, 'VCALENDAR', RCMAIL_CHARSET, false);
$buffer = '';
}
}
fclose($fp);
if ($data = $parser->getComponents()) {
foreach ($data as $comp) {
if ($comp->getType() == 'vEvent') {
$event = $this->_to_rcube_format($comp);
if (!$seen[$event['uid']]++)
$this->events[] = $event;
}
}
}
return $this->events;
}
/**
* Load iCal parser from the Horde lib
*/
private function get_parser()
{
// use Horde:iCalendar to parse vcalendar file format
- require_once 'Horde/iCalendar.php';
+ require_once($this->cal->home . '/lib/Horde_iCalendar.php');
// set target charset for parsed events
$GLOBALS['_HORDE_STRING_CHARSET'] = RCMAIL_CHARSET;
return new Horde_iCalendar;
}
/**
* Convert the given File_IMC_Parse_Vcalendar_Event object to the internal event format
*/
private function _to_rcube_format($ve)
{
$event = array(
'uid' => $ve->getAttributeDefault('UID'),
'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
'title' => $ve->getAttributeDefault('SUMMARY'),
'start' => $ve->getAttribute('DTSTART'),
'end' => $ve->getAttribute('DTEND'),
// set defaults
'free_busy' => 'busy',
'priority' => 0,
);
// check for all-day dates
if (is_array($event['start'])) {
// create timestamp at 12:00 in user's timezone
$event['start'] = $this->_date2time($event['start']);
$event['allday'] = true;
}
if (is_array($event['end'])) {
$event['end'] = $this->_date2time($event['end']) - 23 * 3600;
}
// map other attributes to internal fields
$_attendees = array();
foreach ($ve->getAllAttributes() as $attr) {
switch ($attr['name']) {
case 'ORGANIZER':
$organizer = array(
'name' => $attr['params']['CN'],
'email' => preg_replace('/^mailto:/i', '', $attr['value']),
'role' => 'ORGANIZER',
'status' => 'ACCEPTED',
);
if (isset($_attendees[$organizer['email']])) {
$i = $_attendees[$organizer['email']];
$event['attendees'][$i]['role'] = $organizer['role'];
}
break;
case 'ATTENDEE':
$attendee = array(
'name' => $attr['params']['CN'],
'email' => preg_replace('/^mailto:/i', '', $attr['value']),
'role' => $attr['params']['ROLE'] ? $attr['params']['ROLE'] : 'REQ-PARTICIPANT',
'status' => $attr['params']['PARTSTAT'],
'rsvp' => $attr['params']['RSVP'] == 'TRUE',
);
if ($organizer && $organizer['email'] == $attendee['email'])
$attendee['role'] = 'ORGANIZER';
$event['attendees'][] = $attendee;
$_attendees[$attendee['email']] = count($event['attendees']) - 1;
break;
case 'TRANSP':
$event['free_busy'] = $attr['value'] == 'TRANSPARENT' ? 'free' : 'busy';
break;
case 'STATUS':
if ($attr['value'] == 'TENTATIVE')
$event['free_busy'] == 'tentative';
break;
case 'PRIORITY':
if (is_numeric($attr['value'])) {
$event['priority'] = $attr['value'];
}
break;
case 'RRULE':
// parse recurrence rule attributes
foreach (explode(';', $attr['value']) as $par) {
list($k, $v) = explode('=', $par);
$params[$k] = $v;
}
if ($params['UNTIL'])
$params['UNTIL'] = $ve->_parseDateTime($params['UNTIL']);
if (!$params['INTERVAL'])
$params['INTERVAL'] = 1;
$event['recurrence'] = $params;
break;
case 'EXDATE':
break;
case 'RECURRENCE-ID':
$event['recurrence_id'] = $this->_date2time($attr['value']);
break;
case 'SEQUENCE':
$event['sequence'] = intval($attr['value']);
break;
case 'DESCRIPTION':
case 'LOCATION':
$event[strtolower($attr['name'])] = $attr['value'];
break;
case 'CLASS':
case 'X-CALENDARSERVER-ACCESS':
$sensitivity_map = array('PUBLIC' => 0, 'PRIVATE' => 1, 'CONFIDENTIAL' => 2);
$event['sensitivity'] = $sensitivity_map[$attr['value']];
break;
case 'X-MICROSOFT-CDO-BUSYSTATUS':
if ($attr['value'] == 'OOF')
$event['free_busy'] == 'outofoffice';
else if (in_array($attr['value'], array('FREE', 'BUSY', 'TENTATIVE')))
$event['free_busy'] = strtolower($attr['value']);
break;
}
}
// find alarms
if ($valarm = $ve->findComponent('valarm')) {
$action = 'DISPLAY';
$trigger = null;
foreach ($valarm->getAllAttributes() as $attr) {
switch ($attr['name']) {
case 'TRIGGER':
if ($attr['params']['VALUE'] == 'DATE-TIME') {
$trigger = '@' . $attr['value'];
}
else {
$trigger = $attr['value'];
$offset = abs($trigger);
$unit = 'S';
if ($offset % 86400 == 0) {
$unit = 'D';
$trigger = intval($trigger / 86400);
}
else if ($offset % 3600 == 0) {
$unit = 'H';
$trigger = intval($trigger / 3600);
}
else if ($offset % 60 == 0) {
$unit = 'M';
$trigger = intval($trigger / 60);
}
}
break;
case 'ACTION':
$action = $attr['value'];
break;
}
}
if ($trigger)
$event['alarms'] = $trigger . $unit . ':' . $action;
}
// add organizer to attendees list if not already present
if ($organizer && !isset($_attendees[$organizer['email']]))
array_unshift($event['attendees'], $organizer);
// make sure the event has an UID
if (!$event['uid'])
$event['uid'] = $this->cal->$this->generate_uid();
return $event;
}
/**
* Helper method to correctly interpret an all-day date value
*/
private function _date2time($prop)
{
// create timestamp at 12:00 in user's timezone
if (is_array($prop)) {
$date = new DateTime(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone);
console($prop, $date->format('r'));
return $date->getTimestamp();
}
return $prop;
}
/**
* Free resources by clearing member vars
*/
public function reset()
{
$this->method = '';
$this->events = array();
}
/**
* Export events to iCalendar format
*
* @param array Events as array
* @param string VCalendar method to advertise
* @param boolean Directly send data to stdout instead of returning
* @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
*/
public function export($events, $method = null, $write = false)
{
$ical = "BEGIN:VCALENDAR" . self::EOL;
$ical .= "VERSION:2.0" . self::EOL;
$ical .= "PRODID:-//Roundcube Webmail " . RCMAIL_VERSION . "//NONSGML Calendar//EN" . self::EOL;
$ical .= "CALSCALE:GREGORIAN" . self::EOL;
if ($method)
$ical .= "METHOD:" . strtoupper($method) . self::EOL;
if ($write) {
echo $ical;
$ical = '';
}
foreach ($events as $event) {
$vevent = "BEGIN:VEVENT" . self::EOL;
$vevent .= "UID:" . self::escpape($event['uid']) . self::EOL;
$vevent .= "DTSTAMP:" . gmdate('Ymd\THis\Z', $event['changed'] ? $event['changed'] : time()) . self::EOL;
// correctly set all-day dates
if ($event['allday']) {
$vevent .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
$vevent .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 86400) . self::EOL; // ends the next day
}
else {
$vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
$vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
}
$vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
$vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
if (!empty($event['attendees'])){
$vevent .= $this->_get_attendees($event['attendees']);
}
if (!empty($event['location'])) {
$vevent .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
}
if ($event['recurrence']) {
$vevent .= "RRULE:" . calendar::to_rrule($event['recurrence'], self::EOL) . self::EOL;
}
if(!empty($event['categories'])) {
$vevent .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL;
}
if ($event['sensitivity'] > 0) {
$vevent .= "CLASS:" . ($event['sensitivity'] == 2 ? 'CONFIDENTIAL' : 'PRIVATE') . self::EOL;
}
if ($event['alarms']) {
list($trigger, $action) = explode(':', $event['alarms']);
$val = calendar::parse_alaram_value($trigger);
$vevent .= "BEGIN:VALARM\n";
if ($val[1]) $vevent .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL;
else $vevent .= "TRIGGER;VALUE=DATE-TIME:" . gmdate('Ymd\THis\Z', $val[0]) . self::EOL;
if ($action) $vevent .= "ACTION:" . self::escpape(strtoupper($action)) . self::EOL;
$vevent .= "END:VALARM\n";
}
$vevent .= "TRANSP:" . ($event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE') . self::EOL;
if ($event['priority']) {
$vevent .= "PRIORITY:" . $event['priority'] . self::EOL;
}
if ($event['cancelled'])
$vevent .= "STATUS:CANCELLED" . self::EOL;
else if ($event['free_busy'] == 'tentative')
$vevent .= "STATUS:TENTATIVE" . self::EOL;
// TODO: export attachments
$vevent .= "END:VEVENT" . self::EOL;
if ($write)
echo rcube_vcard::rfc2425_fold($vevent);
else
$ical .= $vevent;
}
$ical .= "END:VCALENDAR" . self::EOL;
if ($write) {
echo $ical;
return true;
}
// fold lines to 75 chars
return rcube_vcard::rfc2425_fold($ical);
}
private function escpape($str)
{
return preg_replace('/(?<!\\\\)([\:\;\,\\n\\r])/', '\\\$1', $str);
}
/**
* Construct the orginizer of the event.
* @param Array Attendees and roles
*
*/
private function _get_attendees($ats)
{
$organizer = "";
$attendees = "";
foreach ($ats as $at) {
if ($at['role']=="ORGANIZER") {
//I am an orginizer
$organizer .= "ORGANIZER;";
if (!empty($at['name']))
$organizer .= 'CN="' . $at['name'] . '"';
$organizer .= ":mailto:". $at['email'] . self::EOL;
}
else {
//I am an attendee
$attendees .= "ATTENDEE;ROLE=" . $at['role'] . ";PARTSTAT=" . $at['status'];
if ($at['rsvp'])
$attendees .= ";RSVP=TRUE";
if (!empty($at['name']))
$attendees .= ';CN="' . $at['name'] . '"';
$attendees .= ":mailto:" . $at['email'] . self::EOL;
}
}
return $organizer . $attendees;
}
}
diff --git a/plugins/calendar/lib/get_horde_icalendar.sh b/plugins/calendar/lib/get_horde_icalendar.sh
new file mode 100755
index 00000000..d076af51
--- /dev/null
+++ b/plugins/calendar/lib/get_horde_icalendar.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Copy Horde_iCalendar classes and dependencies to stdout.
+# This will create a standalone copy of the classes requried for iCal parsing.
+
+SRCDIR=$1
+
+if [ ! -d "$SRCDIR" ]; then
+ echo "Usage: get_horde_icalendar.sh SRCDIR"
+ echo "Please enter a valid source directory of the Horde lib"
+ exit 1
+fi
+
+echo "<?php\n"
+echo "require_once(dirname(__FILE__) . '/Horde_Date.php');"
+
+sed 's/<?php//; s/?>//' $SRCDIR/String.php
+echo "\n"
+sed 's/<?php//; s/?>//' $SRCDIR/iCalendar.php | sed -E "s/include_once.+//; s/NLS::getCharset\(\)/'UTF-8'/"
+echo "\n"
+
+for fn in `ls $SRCDIR/iCalendar/*.php | grep -v 'vcard.php'`; do
+ sed 's/<?php//; s/?>//' $fn | sed -E "s/(include|require)_once.+//"
+done;
diff --git a/plugins/calendar/package.xml b/plugins/calendar/package.xml
index 96bbeb01..12844305 100644
--- a/plugins/calendar/package.xml
+++ b/plugins/calendar/package.xml
@@ -1,190 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>calendar</name>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Calendar plugin</summary>
<description>-</description>
<lead>
<name>Thomas Bruederli</name>
<user>bruederli</user>
<email>bruederli@kolabsys.com</email>
<active>yes</active>
</lead>
<developer>
<name>Alensader Machniak</name>
<user>machniak</user>
<email>machniak@kolabsys.com</email>
<active>yes</active>
</developer>
<date>2011-11-01</date>
<version>
<release>0.8</release>
<api>0.8</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license>
<notes>-</notes>
<contents>
<dir baseinstalldir="/" name="/">
<file name="calendar.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="calendar_base.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="calendar_ui.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="print.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/calendar_ical.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/calendar_itip.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/calendar_recurrence.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/calendar_ui.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
- <file name="lib/Horde_Date_Recurrence.php" role="php">
- <tasks:replace from="@name@" to="name" type="package-info"/>
- <tasks:replace from="@package_version@" to="version" type="package-info"/>
- </file>
+ <file name="lib/Horde_Date.php" role="php"></file>
+ <file name="lib/Horde_Date_Recurrence.php" role="php"></file>
+ <file name="lib/Horde_iCalendar.php" role="php"></file>
<file name="lib/fullcalendar-rc.patch" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/js/fullcalendar.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/jquery.miniColors.min.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/calendar_driver.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/kolab/kolab_calendar.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/kolab/kolab_driver.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/database/database_driver.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/database/SQL/mysql.sql" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/database/SQL/postgresql.sql" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/database/SQL/sqlite.sql" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="drivers/kolab/SQL/mysql.sql" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="skins/default/calendar.css" role="data"></file>
<file name="skins/default/fullcalendar.css" role="data"></file>
<file name="skins/default/fullcalendar.print.css" role="data"></file>
<file name="skins/default/iehacks.css" role="data"></file>
<file name="skins/default/jquery.miniColors.css" role="data"></file>
<file name="skins/default/print.css" role="data"></file>
<file name="skins/default/print.iehacks.css" role="data"></file>
<file name="skins/default/images/attendee-status.gif" role="data"></file>
<file name="skins/default/images/badge_confidential.gif" role="data"></file>
<file name="skins/default/images/badge_confidential.png" role="data"></file>
<file name="skins/default/images/badge_private.gif" role="data"></file>
<file name="skins/default/images/badge_private.png" role="data"></file>
<file name="skins/default/images/calendar-blue.png" role="data"></file>
<file name="skins/default/images/calendar.gif" role="data"></file>
<file name="skins/default/images/calendar.png" role="data"></file>
<file name="skins/default/images/calendars.gif" role="data"></file>
<file name="skins/default/images/calendars.png" role="data"></file>
<file name="skins/default/images/eventicons.gif" role="data"></file>
<file name="skins/default/images/export.png" role="data"></file>
<file name="skins/default/images/freebusy-colors.gif" role="data"></file>
<file name="skins/default/images/freebusy-colors.png" role="data"></file>
<file name="skins/default/images/invitation.png" role="data"></file>
<file name="skins/default/images/listheader.gif" role="data"></file>
<file name="skins/default/images/loading_blue.gif" role="data"></file>
<file name="skins/default/images/minicolors-all.png" role="data"></file>
<file name="skins/default/images/minicolors-handles.gif" role="data"></file>
<file name="skins/default/images/preview.png" role="data"></file>
<file name="skins/default/images/print.png" role="data"></file>
<file name="skins/default/images/spacer.gif" role="data"></file>
<file name="skins/default/images/toggle.gif" role="data"></file>
<file name="skins/default/images/toolbar.gif" role="data"></file>
<file name="skins/default/images/toolbar.png" role="data"></file>
<file name="skins/default/templates/attachment.html" role="data"></file>
<file name="skins/default/templates/calendar.html" role="data"></file>
<file name="skins/default/templates/eventedit.html" role="data"></file>
<file name="skins/default/templates/freebusylegend.html" role="data"></file>
<file name="skins/default/templates/itipattend.html" role="data"></file>
<file name="skins/default/templates/kolabacl.html" role="data"></file>
<file name="skins/default/templates/kolabform.html" role="data"></file>
<file name="skins/default/templates/print.html" role="data"></file>
<file name="config.inc.php.dist" role="data"></file>
<file name="LICENSE" role="data"></file>
<file name="README" role="data"></file>
<file name="TODO" role="data"></file>
<file name="localization/bg_BG.inc" role="data"></file>
<file name="localization/cs_CZ.inc" role="data"></file>
<file name="localization/de_CH.inc" role="data"></file>
<file name="localization/de_DE.inc" role="data"></file>
<file name="localization/en_US.inc" role="data"></file>
<file name="localization/es_ES.inc" role="data"></file>
<file name="localization/fr_FR.inc" role="data"></file>
<file name="localization/hu_HU.inc" role="data"></file>
<file name="localization/it_IT.inc" role="data"></file>
<file name="localization/nl_NL.inc" role="data"></file>
<file name="localization/pl_PL.inc" role="data"></file>
<file name="localization/pt_BR.inc" role="data"></file>
<file name="localization/ru_RU.inc" role="data"></file>
</dir>
<!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.2.1</min>
</php>
<pearinstaller>
<min>1.7.0</min>
</pearinstaller>
</required>
</dependencies>
<phprelease/>
</package>

File Metadata

Mime Type
text/x-diff
Expires
Fri, Apr 24, 9:49 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18896463
Default Alt Text
(391 KB)

Event Timeline