diff --git a/plugins/calendar/README b/plugins/calendar/README index e5a38ee1..39cd1928 100644 --- a/plugins/calendar/README +++ b/plugins/calendar/README @@ -16,3 +16,8 @@ are used. They are packaged in a slightly modified version with this plugin. iCalendar parsing is done with the help of the Horde_iCalendar class. A copy of that class with all its dependencies is part of this package. In order to update it, execute lib/get_horde_icalendar.sh > lib/Horde_iCalendar.php + +IMPORTANT +--------- +The calendar module makes heavy use of PHP's DateTime as well as DateInterval +classes. The latter one requires at least PHP 5.3.0 to run. diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 5b1ec0d9..f63ce426 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -8,7 +8,7 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2011, Kolab Systems AG + * Copyright (C) 2012, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -872,7 +872,7 @@ class calendar extends rcube_plugin $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']); $count = $errors = 0; - $rangestart = $_REQUEST['_range'] ? strtotime("now -" . intval($_REQUEST['_range']) . " months") : 0; + $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0; foreach ($events as $event) { // TODO: correctly handle recurring events which start before $rangestart if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart))) @@ -927,6 +927,8 @@ class calendar extends rcube_plugin if ($calendars[$calid]) { $calname = $calendars[$calid]['name'] ? $calendars[$calid]['name'] : $calid; + $calname = preg_replace('/[^a-z0-9_.-]/i', '', html_entity_decode($calname)); // to 7bit ascii + if (empty($calname)) $calname = $calid; $events = $this->driver->load_events($start, $end, null, $calid, 0); } else @@ -1107,12 +1109,17 @@ class calendar extends rcube_plugin } /** - * Convert the given date string into a GMT-based time stamp + * Shift dates into user's current timezone */ - function fromGMT($datetime) + private function adjust_timezone($dt) { - $ts = is_numeric($datetime) ? $datetime : strtotime($datetime); - return $ts + $this->gmt_offset; + if (is_numeric($dt)) + $dt = new DateTime('@'.$td); + else if (is_string($dt)) + $dt = new DateTime($dt); + + $dt->setTimezone($this->timezone); + return $dt; } /** @@ -1139,16 +1146,19 @@ class calendar extends rcube_plugin // compose a human readable strings for alarms_text and recurrence_text if ($event['alarms']) $event['alarms_text'] = $this->_alarms_text($event['alarms']); - if ($event['recurrence']) + if ($event['recurrence']) { $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']); + if ($event['recurrence']['UNTIL']) + $event['recurrence']['UNTIL'] = $this->adjust_timezone($event['recurrence']['UNTIL'])->format('c'); + } foreach ((array)$event['attachments'] as $k => $attachment) { $event['attachments'][$k]['classname'] = rcmail_filetype2classname($attachment['mimetype'], $attachment['name']); } return array( - 'start' => gmdate('c', $this->fromGMT($event['start'])), // client treats date strings as they were in users's timezone - 'end' => gmdate('c', $this->fromGMT($event['end'])), // so shift timestamps to users's timezone and render a date string + 'start' => $this->adjust_timezone($event['start'])->format('c'), + 'end' => $this->adjust_timezone($event['end'])->format('c'), 'title' => strval($event['title']), 'description' => strval($event['description']), 'location' => strval($event['location']), @@ -1167,8 +1177,8 @@ class calendar extends rcube_plugin foreach ($alarms as $alarm) { $out[] = array( 'id' => $alarm['id'], - 'start' => gmdate('c', $this->fromGMT($alarm['start'])), - 'end' => gmdate('c', $this->fromGMT($alarm['end'])), + 'start' => $this->adjust_timezone($alarm['start'])->format('c'), + 'end' => $this->adjust_timezone($alarm['end'])->format('c'), 'allDay' => ($event['allday'] == 1)?true:false, 'title' => $alarm['title'], 'location' => $alarm['location'], @@ -1297,7 +1307,7 @@ class calendar extends rcube_plugin } $offset = $notify[0] * $mult; $refdate = $mult > 0 ? $event['end'] : $event['start']; - $notify_at = $refdate + $offset; + $notify_at = $refdate->format('U') + $offset; } else { // absolute timestamp $notify_at = $notify[0]; @@ -1319,11 +1329,11 @@ class calendar extends rcube_plugin $k = strtoupper($k); switch ($k) { case 'UNTIL': - $val = gmdate('Ymd\THis', $val); + $val = $val->format('Ymd\THis'); break; case 'EXDATE': foreach ((array)$val as $i => $ex) - $val[$i] = gmdate('Ymd\THis', $ex); + $val[$i] = $ex->format('Ymd\THis'); $val = join(',', (array)$val); break; } @@ -1432,8 +1442,8 @@ class calendar extends rcube_plugin $this->driver->new_event(array( 'uid' => $this->generate_uid(), - 'start' => $start, - 'end' => $start + $duration, + 'start' => new DateTime('@'.$start), + 'end' => new DateTime('@'.($start + $duration)), 'allday' => $allday, 'title' => rtrim($title), 'free_busy' => $fb == 2 ? 'outofoffice' : ($fb ? 'busy' : 'free'), @@ -1683,6 +1693,13 @@ class calendar extends rcube_plugin */ private function prepare_event(&$event, $action) { + // convert dates into DateTime objects in user's current timezone + $event['start'] = new DateTime($event['start'], $this->timezone); + $event['end'] = new DateTime($event['end'], $this->timezone); + + if ($event['recurrence']['UNTIL']) + $event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->timezone); + $attachments = array(); $eventid = 'cal:'.$event['id']; if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) { @@ -1785,7 +1802,7 @@ class calendar extends rcube_plugin public function event_date_text($event, $tzinfo = false) { $fromto = ''; - $duration = $event['end'] - $event['start']; + $duration = $event['start']->diff($event['end'])->format('s'); $this->date_format_defaults(); $date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])); @@ -1796,7 +1813,7 @@ class calendar extends rcube_plugin if (($todate = format_date($event['end'], $date_format)) != $fromto) $fromto .= ' - ' . $todate; } - else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) { + else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) { $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) . ' - ' . format_date($event['end'], $time_format); } diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js index 7ece1241..54388bd3 100644 --- a/plugins/calendar/calendar_base.js +++ b/plugins/calendar/calendar_base.js @@ -6,7 +6,7 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2011, Kolab Systems AG + * Copyright (C) 2012, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 8e84abc4..ba52c25c 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -186,6 +186,13 @@ function rcube_calendar_ui(settings) return d; }; + // turn the given date into an ISO 8601 date string understandable by PHPs strtotime() + var date2servertime = function(date) + { + return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate() + + 'T'+date.getHours()+':'+date.getMinutes()+':'+date.getSeconds(); + } + // convert the given Date object into a unix timestamp respecting browser's and user's timezone settings var date2unixtime = function(date) { @@ -545,7 +552,7 @@ function rcube_calendar_ui(settings) recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change(); interval = $('select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1); rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1); - rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(new Date(event.recurrence.UNTIL*1000), settings['date_format']) : ''); + rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate($.fullCalendar.parseISO8601(event.recurrence.UNTIL), settings['date_format']) : ''); $('input.edit-recurrence-until:checked').prop('checked', false); var weekdays = ['SU','MO','TU','WE','TH','FR','SA']; @@ -642,8 +649,8 @@ function rcube_calendar_ui(settings) // post data to server var data = { calendar: event.calendar, - start: date2unixtime(start), - end: date2unixtime(end), + start: date2servertime(start), + end: date2servertime(end), allday: allday.checked?1:0, title: title.val(), description: description.val(), @@ -702,7 +709,7 @@ function rcube_calendar_ui(settings) if (until == 'count') data.recurrence.COUNT = rrtimes.val(); else if (until == 'until') - data.recurrence.UNTIL = date2unixtime(parse_datetime(endtime.val(), rrenddate.val())); + data.recurrence.UNTIL = date2servertime(parse_datetime(endtime.val(), rrenddate.val())); if (freq == 'WEEKLY') { var byday = []; @@ -2233,9 +2240,9 @@ function rcube_calendar_ui(settings) // initalize the fullCalendar plugin var fc = $('#calendar').fullCalendar({ header: { - left: 'prev,next today', + right: 'prev,next today', center: 'title', - right: 'agendaDay,agendaWeek,month,table' + left: 'agendaDay,agendaWeek,month,table' }, aspectRatio: 1, date: viewdate.getDate(), @@ -2375,8 +2382,8 @@ function rcube_calendar_ui(settings) var data = { id: event.id, calendar: event.calendar, - start: date2unixtime(event.start), - end: date2unixtime(event.end), + start: date2servertime(event.start), + end: date2servertime(event.end), allday: allDay?1:0 }; update_event_confirm('move', event, data); @@ -2393,8 +2400,8 @@ function rcube_calendar_ui(settings) var data = { id: event.id, calendar: event.calendar, - start: date2unixtime(event.start), - end: date2unixtime(event.end) + start: date2servertime(event.start), + end: date2servertime(event.end) }; update_event_confirm('resize', event, data); }, diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index efc3c11c..7feca605 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -8,7 +8,7 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2011, Kolab Systems AG + * Copyright (C) 2012, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -32,20 +32,20 @@ * 'id' => 'Event ID used for editing', * 'uid' => 'Unique identifier of this event', * 'calendar' => 'Calendar identifier to add event to or where the event is stored', - * 'start' => , // Event start date/time as unix timestamp - * 'end' => , // Event end date/time as unix timestamp + * 'start' => DateTime, // Event start date/time as DateTime object + * 'end' => DateTime, // Event end date/time as DateTime object * 'allday' => true|false, // Boolean flag if this is an all-day event - * 'changed' => , // Last modification date of event + * 'changed' => DateTime, // Last modification date of event * 'title' => 'Event title/summary', * 'location' => 'Location string', * 'description' => 'Event description', * 'recurrence' => array( // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs * 'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY', * 'INTERVAL' => 1...n, - * 'UNTIL' => , + * 'UNTIL' => DateTime, * 'COUNT' => 1..n, // number of times * // + more properties (see http://www.kanzaki.com/docs/ical/recur.html) - * 'EXDATE' => array(), // list of s of exception Dates/Times + * 'EXDATE' => array(), // list of DateTime objects of exception Dates/Times * ), * 'recurrence_id' => 'ID of the recurrence group', // usually the ID of the starting event * 'categories' => 'Event category', @@ -157,8 +157,8 @@ abstract class calendar_driver * * @param array Hash array with event properties: * id: Event identifier - * start: Event start date/time as unix timestamp - * end: Event end date/time as unix timestamp + * start: Event start date/time as DateTime object + * end: Event end date/time as DateTime object * allday: Boolean flag if this is an all-day event * @return boolean True on success, False on error */ @@ -169,8 +169,8 @@ abstract class calendar_driver * * @param array Hash array with event properties: * id: Event identifier - * start: Event start date/time as unix timestamp in user timezone - * end: Event end date/time as unix timestamp in user timezone + * start: Event start date/time as DateTime object with timezone + * end: Event end date/time as DateTime object with timezone * @return boolean True on success, False on error */ abstract function resize_event($event); @@ -230,8 +230,8 @@ abstract class calendar_driver * @return array A list of alarms, each encoded as hash array: * id: Event identifier * uid: Unique identifier of this event - * start: Event start date/time as unix timestamp - * end: Event end date/time as unix timestamp + * start: Event start date/time as DateTime object + * end: Event end date/time as DateTime object * allday: Boolean flag if this is an all-day event * title: Event title/summary * location: Location string diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index b871d512..039276ed 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -8,7 +8,7 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2011, Kolab Systems AG + * Copyright (C) 2012, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -27,6 +27,8 @@ class database_driver extends calendar_driver { + const DB_DATE_FORMAT = 'Y-m-d H:i:s'; + // features this backend supports public $alarms = true; public $attendees = true; @@ -40,6 +42,7 @@ class database_driver extends calendar_driver private $calendars = array(); private $calendar_ids = ''; private $free_busy_map = array('free' => 0, 'busy' => 1, 'out-of-office' => 2, 'outofoffice' => 2, 'tentative' => 3); + private $server_timezone; private $db_events = 'events'; private $db_calendars = 'calendars'; @@ -56,6 +59,7 @@ class database_driver extends calendar_driver { $this->cal = $cal; $this->rc = $cal->rc; + $this->server_timezone = new DateTimeZone(date_default_timezone_get()); // load library classes require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php'); @@ -215,14 +219,14 @@ class database_driver extends calendar_driver $query = $this->rc->db->query(sprintf( "INSERT INTO " . $this->db_events . " (calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, attendees, alarms, notifyat) - VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + VALUES (?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $this->rc->db->now(), - $this->rc->db->now(), - $this->rc->db->fromunixtime($event['start']), - $this->rc->db->fromunixtime($event['end']) + $this->rc->db->now() ), $event['calendar'], strval($event['uid']), + $event['start']->format(self::DB_DATE_FORMAT), + $event['end']->format(self::DB_DATE_FORMAT), intval($event['all_day']), $event['_recurrence'], strval($event['title']), @@ -299,20 +303,23 @@ class database_driver extends calendar_driver case 'future': if ($master['id'] != $event['id']) { // set until-date on master event, then save this instance as new recurring event - $master['recurrence']['UNTIL'] = $event['start'] - 86400; + $master['recurrence']['UNTIL'] = clone $event['start']; + $master['recurrence']['UNTIL']->sub(new DateInterval('P1D')); unset($master['recurrence']['COUNT']); $update_master = true; - + // if recurrence COUNT, update value to the correct number of future occurences if ($event['recurrence']['COUNT']) { + $fromdate = clone $master['start']; + $fromdate->setTimezone($this->server_timezone); $sqlresult = $this->rc->db->query(sprintf( "SELECT event_id FROM " . $this->db_events . " WHERE calendar_id IN (%s) - AND start >= %s + AND start >= ? AND recurrence_id=?", - $this->calendar_ids, - $this->rc->db->fromunixtime($event['start']) + $this->calendar_ids ), + $fromdate->format(self::DB_DATE_FORMAT), $master['id']); if ($count = $this->rc->db->num_rows($sqlresult)) $event['recurrence']['COUNT'] = $count; @@ -329,20 +336,21 @@ class database_driver extends calendar_driver $event['recurrence_id'] = 0; // use start date from master but try to be smart on time or duration changes - $old_start_date = date('Y-m-d', $old['start']); - $old_start_time = date('H:i', $old['start']); - $old_duration = $old['end'] - $old['start']; + $old_start_date = $old['start']->format('Y-m-d'); + $old_start_time = $old['start']->format('H:i'); + $old_duration = $old['end']->format('U') - $old['start']->format('U'); - $new_start_date = date('Y-m-d', $event['start']); - $new_start_time = date('H:i', $event['start']); - $new_duration = $event['end'] - $event['start']; + $new_start_date = $event['start']->format('Y-m-d'); + $new_start_time = $event['start']->format('H:i'); + $new_duration = $event['end']->format('U') - $event['start']->format('U'); $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration; // shifted or resized if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) { - $event['start'] = $master['start'] + ($event['start'] - $old['start']); - $event['end'] = $event['start'] + $new_duration; + $event['start'] = $master['start']->add($old['start']->diff($event['start'])); + $event['end'] = clone $event['start']; + $event['end']->add(new DateInterval('PT'.$new_duration.'S')); } break; } @@ -363,6 +371,12 @@ class database_driver extends calendar_driver */ private function _save_preprocess($event) { + // shift dates to server's timezone + $event['start'] = clone $event['start']; + $event['start']->setTimezone($this->server_timezone); + $event['end'] = clone $event['end']; + $event['end']->setTimezone($this->server_timezone); + // compose vcalendar-style recurrencue rule from structured data $rrule = $event['recurrence'] ? calendar::to_rrule($event['recurrence']) : ''; $event['_recurrence'] = rtrim($rrule, ';'); @@ -396,7 +410,7 @@ class database_driver extends calendar_driver */ private function _get_notification($event) { - if ($event['alarms'] && $event['start'] > time()) { + if ($event['alarms'] && $event['start'] > new DateTime()) { $alarm = calendar::get_next_alarm($event); if ($alarm['time'] && $alarm['action'] == 'DISPLAY') @@ -416,9 +430,11 @@ class database_driver extends calendar_driver { $event = $this->_save_preprocess($event); $sql_set = array(); - $set_cols = array('all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat'); + $set_cols = array('start', 'end', 'all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat'); foreach ($set_cols as $col) { - if (isset($event[$col])) + if (is_object($event[$col]) && is_a($event[$col], 'DateTime')) + $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT)); + else if (isset($event[$col])) $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]); } @@ -430,12 +446,10 @@ class database_driver extends calendar_driver $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " - SET changed=%s, start=%s, end=%s %s + SET changed=%s %s WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->now(), - $this->rc->db->fromunixtime($event['start']), - $this->rc->db->fromunixtime($event['end']), ($sql_set ? ', ' . join(', ', $sql_set) : '') ), $event['id'] @@ -489,21 +503,24 @@ class database_driver extends calendar_driver require_once($this->cal->home . '/lib/calendar_recurrence.php'); $recurrence = new calendar_recurrence($this->cal, $event); - - $duration = $event['end'] - $event['start']; - while ($next_ts = $recurrence->next_start()) { - $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_ts, 'end' => $next_ts + $duration)); + + $duration = $event['start']->diff($event['end']); + while ($next_start = $recurrence->next_start()) { + $next_start->setTimezone($this->server_timezone); + $next_end = clone $next_start; + $next_end->add($duration); + $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_start, 'end' => $next_end)); $query = $this->rc->db->query(sprintf( "INSERT INTO " . $this->db_events . " (calendar_id, recurrence_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, notifyat) - SELECT calendar_id, ?, %s, %s, uid, %s, %s, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ? + SELECT calendar_id, ?, %s, %s, uid, ?, ?, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ? FROM " . $this->db_events . " WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->now(), - $this->rc->db->now(), - $this->rc->db->fromunixtime($next_ts), - $this->rc->db->fromunixtime($next_ts + $duration) + $this->rc->db->now() ), $event['id'], + $next_start->format(self::DB_DATE_FORMAT), + $next_end->format(self::DB_DATE_FORMAT), $notify_at, $event['id'] ); @@ -582,16 +599,20 @@ class database_driver extends calendar_driver case 'future': if ($master['id'] != $event['id']) { // set until-date on master event - $master['recurrence']['UNTIL'] = $event['start'] - 86400; + $master['recurrence']['UNTIL'] = clone $event['start']; + $master['recurrence']['UNTIL']->sub(new DateInterval('P1D')); unset($master['recurrence']['COUNT']); $update_master = true; // delete this and all future instances + $fromdate = clone $old['start']; + $fromdate->setTimezone($this->server_timezone); $query = $this->rc->db->query( "DELETE FROM " . $this->db_events . " WHERE calendar_id IN (" . $this->calendar_ids . ") - AND start >= " . $this->rc->db->fromunixtime($old['start']) . " + AND start >= ? AND recurrence_id=?", + $fromdate->format(self::DB_DATE_FORMAT), $master['id'] ); break; @@ -704,8 +725,8 @@ class database_driver extends calendar_driver $free_busy_map = array_flip($this->free_busy_map); $event['id'] = $event['event_id']; - $event['start'] = strtotime($event['start']); - $event['end'] = strtotime($event['end']); + $event['start'] = new DateTime($event['start']); + $event['end'] = new DateTime($event['end']); $event['allday'] = intval($event['all_day']); $event['changed'] = strtotime($event['changed']); $event['free_busy'] = $free_busy_map[$event['free_busy']]; @@ -719,9 +740,9 @@ class database_driver extends calendar_driver if (is_numeric($rr[2])) $rr[2] = intval($rr[2]); else if ($rr[1] == 'UNTIL') - $rr[2] = strtotime($rr[2]); + $rr[2] = date_create($rr[2]); else if ($rr[1] == 'EXDATE') - $rr[2] = array_map('strtotime', explode(',', $rr[2])); + $rr[2] = array_map('date_create', explode(',', $rr[2])); $event['recurrence'][$rr[1]] = $rr[2]; } } @@ -734,7 +755,7 @@ class database_driver extends calendar_driver $attendees = array(); foreach (explode("\n", $event['attendees']) as $line) { $att = array(); - foreach (rcube_explode_quoted_string(';', $line) as $prop) { + foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) { list($key, $value) = explode("=", $prop); $att[strtolower($key)] = stripslashes(trim($value, '""')); } @@ -793,7 +814,7 @@ class database_driver extends calendar_driver public function dismiss_alarm($event_id, $snooze = 0) { // set new notifyat time or unset if not snoozed - $notify_at = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null; + $notify_at = $snooze > 0 ? date(self::DB_DATE_FORMAT, time() + $snooze) : null; $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index 21c94fd3..91ba5b8c 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -182,7 +182,9 @@ class kolab_calendar $this->events[$master_id] = $this->_to_rcube_event($record); if (($master = $this->events[$master_id]) && $master['recurrence']) { - $this->_get_recurring_events($master, $master['start'], $master['start'] + 86400 * 365 * 10, $id); + $limit = clone $master['start']; + $limit->add(new DateInterval('P10Y')); + $this->_get_recurring_events($record, $master['start'], $limit, $id); } } @@ -200,6 +202,10 @@ class kolab_calendar */ public function list_events($start, $end, $search = null, $virtual = 1, $query = array()) { + // convert to DateTime for comparisons + $start = new DateTime('@'.$start); + $end = new DateTime('@'.$end); + // query Kolab storage $query[] = array('dtstart', '<=', $end); $query[] = array('dtend', '>=', $start); @@ -211,13 +217,11 @@ class kolab_calendar } } + $events = array(); foreach ((array)$this->storage->select($query) as $record) { $event = $this->_to_rcube_event($record); $this->events[$event['id']] = $event; - } - $events = array(); - foreach ($this->events as $id => $event) { // remember seen categories if ($event['categories']) $this->categories[$event['categories']]++; @@ -249,9 +253,8 @@ class kolab_calendar } // resolve recurring events - if ($event['recurrence'] && $virtual == 1) { - unset($event['_attendees']); - $events = array_merge($events, $this->_get_recurring_events($event, $start, $end)); + if ($record['recurrence'] && $virtual == 1) { + $events = array_merge($events, $this->_get_recurring_events($record, $start, $end)); } } @@ -374,30 +377,29 @@ class kolab_calendar public function _get_recurring_events($event, $start, $end, $event_id = null) { $recurrence = new kolab_date_recurrence($event); - - $events = array(); - $duration = $event['end'] - $event['start']; + $i = 0; - while ($rec_start = $recurrence->next_start(true)) { - $rec_end = $rec_start + $duration; - $rec_id = $event['id'] . '-' . ++$i; - + $events = array(); + while ($next_event = $recurrence->next_instance()) { + $rec_start = $next_event['start']->format('U'); + $rec_end = $next_event['end']->format('U'); + $rec_id = $event['uid'] . '-' . ++$i; + // add to output if in range - if (($rec_start <= $end && $rec_end >= $start) || ($event_id && $rec_id == $event_id)) { - $rec_event = $event; + if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) { + $rec_event = $this->_to_rcube_event($next_event); $rec_event['id'] = $rec_id; - $rec_event['recurrence_id'] = $event['id']; - $rec_event['start'] = $rec_start; - $rec_event['end'] = $rec_end; + $rec_event['recurrence_id'] = $event['uid']; $rec_event['_instance'] = $i; + unset($rec_event['_attendees']); $events[] = $rec_event; - + if ($rec_id == $event_id) { $this->events[$rec_id] = $rec_event; break; } } - else if ($rec_start > $end) // stop loop if out of range + else if ($next_event['start'] > $end) // stop loop if out of range break; } @@ -411,16 +413,18 @@ class kolab_calendar { $record['id'] = $record['uid']; $record['calendar'] = $this->id; - +/* // convert from DateTime to unix timestamp if (is_a($record['start'], 'DateTime')) $record['start'] = $record['start']->format('U'); if (is_a($record['end'], 'DateTime')) $record['end'] = $record['end']->format('U'); - +*/ // all-day events go from 12:00 - 13:00 - if ($record['end'] <= $record['start'] && $record['allday']) - $record['end'] = $record['start'] + 3600; + if ($record['end'] <= $record['start'] && $record['allday']) { + $record['end'] = clone $record['start']; + $record['end']->add(new DateTimeInterval('PT1H')); + } if (!empty($record['_attachments'])) { foreach ($record['_attachments'] as $key => $attachment) { diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 104984df..c4b233a4 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -380,7 +380,9 @@ class kolab_driver extends calendar_driver // removing the first instance => just move to next occurence if ($master['id'] == $event['id']) { - $recurring = reset($storage->_get_recurring_events($event, $event['start'], $event['end'] + 86400 * 370, $event['id'].'-1')); + $limit = clone $event['end']; + $limit->add(new DateInterval('P370D')); + $recurring = reset($storage->_get_recurring_events($event, $event['start'], $limit, $event['id'].'-1')); $master['start'] = $recurring['start']; $master['end'] = $recurring['end']; if ($master['recurrence']['COUNT']) @@ -397,7 +399,8 @@ class kolab_driver extends calendar_driver $_SESSION['calendar_restore_event_data'] = $master; // set until-date on master event - $master['recurrence']['UNTIL'] = $event['start'] - 86400; + $master['recurrence']['UNTIL'] = clone $event['start']; + $master['recurrence']['UNTIL']->sub(new DateInterval('P1D')); unset($master['recurrence']['COUNT']); $success = $storage->update_event($master); break; @@ -539,7 +542,8 @@ class kolab_driver extends calendar_driver case 'future': if ($master['id'] != $event['id']) { // set until-date on master event - $master['recurrence']['UNTIL'] = $old['start'] - 86400; + $master['recurrence']['UNTIL'] = clone $old['start']; + $master['recurrence']['UNTIL']->sub(new DateInterval('P1D')); unset($master['recurrence']['COUNT']); $storage->update_event($master); @@ -555,7 +559,7 @@ class kolab_driver extends calendar_driver // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::insert_event() if (strlen($event['recurrence']['BYDAY']) == 2) unset($event['recurrence']['BYDAY']); - if ($master['recurrence']['BYMONTH'] == gmdate('n', $master['start'])) + if ($master['recurrence']['BYMONTH'] == $master['start']->format('n')) unset($event['recurrence']['BYMONTH']); $success = $storage->insert_event($event); @@ -567,26 +571,27 @@ class kolab_driver extends calendar_driver $event['uid'] = $master['uid']; // use start date from master but try to be smart on time or duration changes - $old_start_date = date('Y-m-d', $old['start']); - $old_start_time = date('H:i', $old['start']); - $old_duration = $old['end'] - $old['start']; + $old_start_date = $old['start']->format('Y-m-d'); + $old_start_time = $old['start']->format('H:i'); + $old_duration = $old['end']->format('U') - $old['start']->format('U'); - $new_start_date = date('Y-m-d', $event['start']); - $new_start_time = date('H:i', $event['start']); - $new_duration = $event['end'] - $event['start']; + $new_start_date = $event['start']->format('Y-m-d'); + $new_start_time = $event['start']->format('H:i'); + $new_duration = $event['end']->format('U') - $event['start']->format('U'); $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration; // shifted or resized if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) { - $event['start'] = $master['start'] + ($event['start'] - $old['start']); - $event['end'] = $event['start'] + $new_duration; + $event['start'] = $master['start']->add($old['start']->diff($event['start'])); + $event['end'] = clone $event['start']; + $event['end']->add(new DateInterval('PT'.$new_duration.'S')); // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::update_event() if ($old_start_date != $new_start_date) { if (strlen($event['recurrence']['BYDAY']) == 2) unset($event['recurrence']['BYDAY']); - if ($old['recurrence']['BYMONTH'] == gmdate('n', $old['start'])) + if ($old['recurrence']['BYMONTH'] == $old['start']->format('n')) unset($event['recurrence']['BYMONTH']); } } diff --git a/plugins/calendar/lib/Horde_Date.php b/plugins/calendar/lib/Horde_Date.php index d710d722..4ddd9d5e 100644 --- a/plugins/calendar/lib/Horde_Date.php +++ b/plugins/calendar/lib/Horde_Date.php @@ -1,224 +1,566 @@ self::MASK_YEAR, + 'month' => self::MASK_MONTH, + 'mday' => self::MASK_DAY, + 'hour' => self::MASK_HOUR, + 'min' => self::MASK_MINUTE, + 'sec' => self::MASK_SECOND, + ); + + protected $_formatCache = array(); + + /** + * Builds 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' + * - arrays with keys 'year', 'month', 'mday', 'day' + * 'hour', 'min', 'minute', '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) + * - yyyy-mm-dd hh:mm:ss + * - yyyymmddhhmmss + * - yyyymmddThhmmssZ + * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and + * 03 Mar 1973) * - 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. + * - anything parsed by strtotime()/DateTime. * - * 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. + * @throws Horde_Date_Exception */ - function firstDayOfWeek($week, $year) + public function __construct($date = null, $timezone = null) { - $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; + if (!self::$_supportedSpecs) { + self::$_supportedSpecs = self::$_defaultSpecs; + if (function_exists('nl_langinfo')) { + self::$_supportedSpecs .= 'bBpxX'; + } + } + + if (func_num_args() > 2) { + // Handle args in order: year month day hour min sec tz + $this->_initializeFromArgs(func_get_args()); + return; + } + + $this->_initializeTimezone($timezone); + + if (is_null($date)) { + return; + } + + if (is_string($date)) { + $date = trim($date, '"'); + } + + if (is_object($date)) { + $this->_initializeFromObject($date); + } elseif (is_array($date)) { + $this->_initializeFromArray($date); + } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(?:\.\d+)?(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]; + if ($parts[7]) { + $this->_initializeTimezone('UTC'); + } + } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) && + $parts[2] > 0 && $parts[2] <= 12 && + $parts[3] > 0 && $parts[3] <= 31) { + $this->_year = (int)$parts[1]; + $this->_month = (int)$parts[2]; + $this->_mday = (int)$parts[3]; + $this->_hour = $this->_min = $this->_sec = 0; + } elseif ((string)(int)$date == $date) { + // 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']; } - } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) { - return 30; } else { - return 31; + // Use date_create() so we can catch errors with PHP 5.2. Use + // "new DateTime() once we require 5.3. + $parsed = date_create($date); + if (!$parsed) { + throw new Horde_Date_Exception(sprintf(Horde_Date_Translation::t("Failed to parse time string (%s)"), $date)); + } + $parsed->setTimezone(new DateTimeZone(date_default_timezone_get())); + $this->_year = (int)$parsed->format('Y'); + $this->_month = (int)$parsed->format('m'); + $this->_mday = (int)$parsed->format('d'); + $this->_hour = (int)$parsed->format('H'); + $this->_min = (int)$parsed->format('i'); + $this->_sec = (int)$parsed->format('s'); + $this->_initializeTimezone(date_default_timezone_get()); } } /** - * Return the day of the week (0 = Sunday, 6 = Saturday) of this - * object's date. + * Returns a simple string representation of the date object + * + * @return string This object converted to a string. + */ + public function __toString() + { + try { + return $this->format($this->_defaultFormat); + } catch (Exception $e) { + return ''; + } + } + + /** + * Returns a DateTime object representing this object. + * + * @return DateTime + */ + public function toDateTime() + { + $date = new DateTime(null, new DateTimeZone($this->_timezone)); + $date->setDate($this->_year, $this->_month, $this->_mday); + $date->setTime($this->_hour, $this->_min, $this->_sec); + return $date; + } + + /** + * 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. + * + * Algorithm is from PEAR::Date_Calc + * + * @author Monte Ohrt + * @author Pierre-Alain Joye + * @author Daniel Convissor + * @author C.A. Woodcock + * + * @return integer The number of days since 24th November, 4714 B.C. + */ + public function toDays() + { + if (function_exists('GregorianToJD')) { + return gregoriantojd($this->_month, $this->_mday, $this->_year); + } + + $day = $this->_mday; + $month = $this->_month; + $year = $this->_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; + } + } + + /** + * Converts number 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'. + * + * Algorithm is from PEAR::Date_Calc + * + * @author Monte Ohrt + * @author Pierre-Alain Joye + * @author Daniel Convissor + * @author C.A. Woodcock + * + * @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 Horde_Date A Horde_Date object representing the date. + */ + public static function fromDays($days) + { + if (function_exists('JDToGregorian')) { + list($month, $day, $year) = explode('/', JDToGregorian($days)); + } else { + $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 new Horde_Date($year, $month, $day); + } + + /** + * Getter for the date and time properties. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * + * @return integer The property value, or null if not set. + */ + public function __get($name) + { + if ($name == 'day') { + $name = 'mday'; + } + + return $this->{'_' . $name}; + } + + /** + * Setter for the date and time properties. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * @param integer $value The property value. + */ + public function __set($name, $value) + { + if ($name == 'timezone') { + $this->_initializeTimezone($value); + return; + } + if ($name == 'day') { + $name = 'mday'; + } + + if ($name != 'year' && $name != 'month' && $name != 'mday' && + $name != 'hour' && $name != 'min' && $name != 'sec') { + throw new InvalidArgumentException('Undefined property ' . $name); + } + + $down = $value < $this->{'_' . $name}; + $this->{'_' . $name} = $value; + $this->_correct(self::$_corrections[$name], $down); + $this->_formatCache = array(); + } + + /** + * Returns whether a date or time property exists. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * + * @return boolen True if the property exists and is set. + */ + public function __isset($name) + { + if ($name == 'day') { + $name = 'mday'; + } + return ($name == 'year' || $name == 'month' || $name == 'mday' || + $name == 'hour' || $name == 'min' || $name == 'sec') && + isset($this->{'_' . $name}); + } + + /** + * Adds a number of seconds or units to this date, returning a new Date + * object. + */ + public function add($factor) + { + $d = clone($this); + if (is_array($factor) || is_object($factor)) { + foreach ($factor as $property => $value) { + $d->$property += $value; + } + } else { + $d->sec += $factor; + } + + return $d; + } + + /** + * Subtracts a number of seconds or units from this date, returning a new + * Horde_Date object. + */ + public function sub($factor) + { + if (is_array($factor)) { + foreach ($factor as &$value) { + $value *= -1; + } + } else { + $factor *= -1; + } + + return $this->add($factor); + } + + /** + * Converts this object to a different timezone. + * + * @param string $timezone The new timezone. + * + * @return Horde_Date This object. + */ + public function setTimezone($timezone) + { + $date = $this->toDateTime(); + $date->setTimezone(new DateTimeZone($timezone)); + $this->_timezone = $timezone; + $this->_year = (int)$date->format('Y'); + $this->_month = (int)$date->format('m'); + $this->_mday = (int)$date->format('d'); + $this->_hour = (int)$date->format('H'); + $this->_min = (int)$date->format('i'); + $this->_sec = (int)$date->format('s'); + $this->_formatCache = array(); + return $this; + } + + /** + * Sets the default date format used in __toString() + * + * @param string $format + */ + public function setDefaultFormat($format) + { + $this->_defaultFormat = $format; + } + + /** + * Returns the day of the week (0 = Sunday, 6 = Saturday) of this date. * * @return integer The day of the week. */ - function dayOfWeek() + public function dayOfWeek() { - if ($this->month > 2) { - $month = $this->month - 2; - $year = $this->year; + if ($this->_month > 2) { + $month = $this->_month - 2; + $year = $this->_year; } else { - $month = $this->month + 10; - $year = $this->year - 1; + $month = $this->_month + 10; + $year = $this->_year - 1; } $day = (floor((13 * $month - 1) / 5) + - $this->mday + ($year % 100) + + $this->_mday + ($year % 100) + floor(($year % 100) / 4) + floor(($year / 100) / 4) - 2 * floor($year / 100) + 77); @@ -231,27 +573,19 @@ class Horde_Date { * * @return integer The day of the year. */ - function dayOfYear() + public 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; + return $this->format('z') + 1; } /** * Returns the week of the month. * - * @since Horde 3.2 - * * @return integer The week number. */ - function weekOfMonth() + public function weekOfMonth() { - return ceil($this->mday / 7); + return ceil($this->_mday / 7); } /** @@ -259,60 +593,50 @@ class Horde_Date { * * @return integer The week number. */ - function weekOfYear() + public function weekOfYear() { return $this->format('W'); } /** - * Return the number of weeks in the given year (52 or 53). - * - * @static + * Returns the number of weeks in the given year (52 or 53). * * @param integer $year The year to count the number of weeks in. * * @return integer $numWeeks The number of weeks in $year. */ - function weeksInYear($year) + public static 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 = new Horde_Date($year . '-12-31'); + while ($date->dayOfWeek() != self::DATE_THURSDAY) { --$date->mday; } return $date->weekOfYear(); } /** - * Set the date of this object to the $nth weekday of $weekday. + * Sets 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) + public function setNthWeekday($weekday, $nth = 1) { - if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) { - return false; + if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) { + return; } - $this->mday = 1; + $this->_mday = 1; $first = $this->dayOfWeek(); if ($weekday < $first) { - $this->mday = 8 + $weekday - $first; + $this->_mday = 8 + $weekday - $first; } else { - $this->mday = $weekday - $first + 1; + $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 . "
\n"; + $diff = 7 * $nth - 7; + $this->_mday += $diff; + $this->_correct(self::MASK_DAY, $diff < 0); } /** @@ -320,286 +644,241 @@ class Horde_Date { * * @return boolean Validity, counting leap years, etc. */ - function isValid() + public function isValid() { - if ($this->year < 0 || $this->year > 9999) { - return false; - } - return checkdate($this->month, $this->mday, $this->year); + return ($this->_year >= 0 && $this->_year <= 9999); } /** - * 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 + * Compares 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. + * @param mixed $other 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) + * @return integer == 0 if they are on the same date + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) */ - function compareDate($date) + public function compareDate($other) { - if (!is_object($date) || !is_a($date, 'Horde_Date')) { - $date = new Horde_Date($date); + if (!($other instanceof Horde_Date)) { + $other = new Horde_Date($other); } - if ($this->year != $date->year) { - return $this->year - $date->year; + if ($this->_year != $other->year) { + return $this->_year - $other->year; } - if ($this->month != $date->month) { - return $this->month - $date->month; + if ($this->_month != $other->month) { + return $this->_month - $other->month; } - return $this->mday - $date->mday; + return $this->_mday - $other->mday; } /** - * Compare this to another date object by time, to see which one + * Returns whether this date is after the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is after the other. + */ + public function after($other) + { + return $this->compareDate($other) > 0; + } + + /** + * Returns whether this date is before the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is before the other. + */ + public function before($other) + { + return $this->compareDate($other) < 0; + } + + /** + * Returns whether this date is the same like the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is the same like the other. + */ + public function equals($other) + { + return $this->compareDate($other) == 0; + } + + /** + * Compares 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. + * @param mixed $other 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) + * @return integer == 0 if they are at the same time + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) */ - function compareTime($date) + public function compareTime($other) { - if (!is_object($date) || !is_a($date, 'Horde_Date')) { - $date = new Horde_Date($date); + if (!($other instanceof Horde_Date)) { + $other = new Horde_Date($other); } - if ($this->hour != $date->hour) { - return $this->hour - $date->hour; + if ($this->_hour != $other->hour) { + return $this->_hour - $other->hour; } - if ($this->min != $date->min) { - return $this->min - $date->min; + if ($this->_min != $other->min) { + return $this->_min - $other->min; } - return $this->sec - $date->sec; + return $this->_sec - $other->sec; } /** - * Compare this to another date object, including times, to see + * Compares 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. + * @param mixed $other 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) + * @return integer == 0 if they are equal + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) */ - function compareDateTime($date) + public function compareDateTime($other) { - if (!is_object($date) || !is_a($date, 'Horde_Date')) { - $date = new Horde_Date($date); + if (!($other instanceof Horde_Date)) { + $other = new Horde_Date($other); } - if ($diff = $this->compareDate($date)) { + if ($diff = $this->compareDate($other)) { return $diff; } - return $this->compareTime($date); + return $this->compareTime($other); } /** - * Get the time offset for local time zone. + * Returns number of days between this date and another. + * + * @param Horde_Date $other The other day to diff with. + * + * @return integer The absolute number of days between the two dates. + */ + public function diff($other) + { + return abs($this->toDays() - $other->toDays()); + } + + /** + * Returns 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) + public 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 $colon ? $this->format('P') : $this->format('O'); } /** - * Return the unix timestamp representation of this date. + * Returns the unix timestamp representation of this date. * * @return integer A unix timestamp. */ - function timestamp() + public 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); + if ($this->_year >= 1970 && $this->_year < 2038) { + return mktime($this->_hour, $this->_min, $this->_sec, + $this->_month, $this->_mday, $this->_year); } + return $this->format('U'); } /** - * Return the unix timestamp representation of this date, 12:00am. + * Returns the unix timestamp representation of this date, 12:00am. * * @return integer A unix timestamp. */ - function datestamp() + public 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); + if ($this->_year >= 1970 && $this->_year < 2038) { + return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year); } + $date = new DateTime($this->format('Y-m-d')); + return $date->format('U'); } /** - * Format time using the specifiers available in date() or in the DateTime + * Formats date and time to be passed around as a short url parameter. + * + * @return string Date and time. + */ + public function dateString() + { + return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday); + } + + /** + * Formats date and time to the ISO format used by JSON. + * + * @return string Date and time. + */ + public function toJson() + { + return $this->format(self::DATE_JSON); + } + + /** + * Formats date and time to the RFC 2445 iCalendar DATE-TIME format. + * + * @param boolean $floating Whether to return a floating date-time + * (without time zone information). + * + * @return string Date and time. + */ + public function toiCalendar($floating = false) + { + if ($floating) { + return $this->format('Ymd\THis'); + } + $dateTime = $this->toDateTime(); + $dateTime->setTimezone(new DateTimeZone('UTC')); + return $dateTime->format('Ymd\THis\Z'); + } + + /** + * Formats time using the specifiers available in date() or in the DateTime * class' format() method. * - * @since Horde 3.3 + * To format in languages other than English, use strftime() instead. * * @param string $format * * @return string Formatted time. */ - function format($format) + public 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()); + if (!isset($this->_formatCache[$format])) { + $this->_formatCache[$format] = $this->toDateTime()->format($format); } + return $this->_formatCache[$format]; } /** - * 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 + * Formats date and time using strftime() format. * * @return string strftime() formatted date and time. */ - function strftime($format) + public function strftime($format) { - if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) { + if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) { return strftime($format, $this->timestamp()); } else { return $this->_strftime($format); @@ -607,16 +886,12 @@ class Horde_Date { } /** - * Format date and time using a limited set of the strftime() format. + * Formats date and time using a limited set of the strftime() format. * * @return string strftime() formatted date and time. */ - function _strftime($format) + protected function _strftime($format) { - if (preg_match('/%[bBpxX]/', $format)) { - require_once 'Horde/NLS.php'; - } - return preg_replace( array('/%b/e', '/%B/e', @@ -639,136 +914,199 @@ class Horde_Date { '/%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)', + array('$this->_strftime(Horde_Nls::getLangInfo(constant(\'ABMON_\' . (int)$this->_month)))', + '$this->_strftime(Horde_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)', + '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(Horde_Nls::getLangInfo($this->_hour < 12 ? AM_STR : PM_STR))', '$this->_strftime(\'%H:%M\')', - 'sprintf(\'%02d\', $this->sec)', + '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, + '$this->_strftime(Horde_Nls::getLangInfo(D_FMT))', + '$this->_strftime(Horde_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. + * Corrects any over- or underflows in any of the date's members. * - * @TODO remove in Horde 4 - * - * This does NOT work with pre-1970 daylight saving times. - * - * @static + * @param integer $mask We may not want to correct some overflows. + * @param integer $down Whether to correct the date up or down. */ - function _mktime($hr, $min, $sec, $mon = false, $day = false, - $year = false, $is_dst = false, $is_gmt = false) + protected function _correct($mask = self::MASK_ALLPARTS, $down = false) { - if ($mon === false) { - return $is_gmt - ? @gmmktime($hr, $min, $sec) - : @mktime($hr, $min, $sec); + if ($mask & self::MASK_SECOND) { + if ($this->_sec < 0 || $this->_sec > 59) { + $mask |= self::MASK_MINUTE; + + $this->_min += (int)($this->_sec / 60); + $this->_sec %= 60; + if ($this->_sec < 0) { + $this->_min--; + $this->_sec += 60; + } + } } - 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); + if ($mask & self::MASK_MINUTE) { + if ($this->_min < 0 || $this->_min > 59) { + $mask |= self::MASK_HOUR; + + $this->_hour += (int)($this->_min / 60); + $this->_min %= 60; + if ($this->_min < 0) { + $this->_hour--; + $this->_min += 60; + } + } } - $gmt_different = $is_gmt - ? 0 - : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0)); + if ($mask & self::MASK_HOUR) { + if ($this->_hour < 0 || $this->_hour > 23) { + $mask |= self::MASK_DAY; - $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; + $this->_mday += (int)($this->_hour / 24); + $this->_hour %= 24; + if ($this->_hour < 0) { + $this->_mday--; + $this->_hour += 24; + } + } } - $_day_power = 86400; - $_hour_power = 3600; - $_min_power = 60; + if ($mask & self::MASK_MONTH) { + $this->_correctMonth($down); + /* When correcting the month, always correct the day too. Months + * have different numbers of days. */ + $mask |= self::MASK_DAY; + } - $_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; + if ($mask & self::MASK_DAY) { + while ($this->_mday > 28 && + $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) { + if ($down) { + $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year); } 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]; - } + $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year); + $this->_month++; } + $this->_correctMonth($down); } - - 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; + while ($this->_mday < 1) { + --$this->_month; + $this->_correctMonth($down); + $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year); } - 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; } } -} + /** + * Corrects the current month. + * + * This cannot be done in _correct() because that would also trigger a + * correction of the day, which would result in an infinite loop. + * + * @param integer $down Whether to correct the date up or down. + */ + protected function _correctMonth($down = false) + { + $this->_year += (int)($this->_month / 12); + $this->_month %= 12; + if ($this->_month < 1) { + $this->_year--; + $this->_month += 12; + } + } + /** + * Handles args in order: year month day hour min sec tz + */ + protected function _initializeFromArgs($args) + { + $tz = (isset($args[6])) ? array_pop($args) : null; + $this->_initializeTimezone($tz); + + $args = array_slice($args, 0, 6); + $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0); + $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args); + $date = array_merge($keys, $date); + + $this->_initializeFromArray($date); + } + + protected function _initializeFromArray($date) + { + if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) { + if ($date['year'] > 70) { + $date['year'] += 1900; + } else { + $date['year'] += 2000; + } + } + + 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 (isset($date['day']) && + (string)(int)$date['day'] == $date['day']) { + $this->_mday = (int)$date['day']; + } + // 'minute' key also from Horde_Form_datetime + if (isset($date['minute']) && + (string)(int)$date['minute'] == $date['minute']) { + $this->_min = (int)$date['minute']; + } + + $this->_correct(); + } + + protected function _initializeFromObject($date) + { + if ($date instanceof DateTime) { + $this->_year = (int)$date->format('Y'); + $this->_month = (int)$date->format('m'); + $this->_mday = (int)$date->format('d'); + $this->_hour = (int)$date->format('H'); + $this->_min = (int)$date->format('i'); + $this->_sec = (int)$date->format('s'); + $this->_initializeTimezone($date->getTimezone()->getName()); + } else { + $is_horde_date = $date instanceof Horde_Date; + foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) { + if ($is_horde_date || isset($date->$key)) { + $this->{'_' . $key} = (int)$date->$key; + } + } + if (!$is_horde_date) { + $this->_correct(); + } else { + $this->_initializeTimezone($date->timezone); + } + } + } + + protected function _initializeTimezone($timezone) + { + if (empty($timezone)) { + $timezone = date_default_timezone_get(); + } + $this->_timezone = $timezone; + } + +} diff --git a/plugins/calendar/lib/Horde_Date_Recurrence.php b/plugins/calendar/lib/Horde_Date_Recurrence.php index 829a6b84..a1151046 100644 --- a/plugins/calendar/lib/Horde_Date_Recurrence.php +++ b/plugins/calendar/lib/Horde_Date_Recurrence.php @@ -2,4410 +2,210 @@ /** * This is a concatenated copy of the following files: - * PEAR/Date/Calc.php, Horde/Date/Recurrence.php + * Horde/Date.php, Horde/Date/Utils.php, Horde/Date/Recurrence.php + * Pull the latest version of these files from the PEAR channel of the Horde + * project at http://pear.horde.org by installing the Horde_Date package. */ if (!class_exists('Horde_Date')) - require_once(dirname(__FILE__) . '/Horde_Date.php'); + 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 - * @author Pierre-Alain Joye - * @author Daniel Convissor - * @author C.A. Woodcock - * @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 - * @author Daniel Convissor - * @author C.A. Woodcock - * @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 +// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare +class Horde_Date_Translation { + function t($arg) { return $arg; } + function ngettext($sing, $plur, $num) { return ($num > 1 ? $plur : $sing); } +} - // {{{ 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: - *
-     * %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 '%'
-     * 
- * - * @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: - * - * Date_Calc::weekOfYear7th($day, $month, $year, 0) - * - * 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() +/** + * @category Horde + * @package Date + */ +/** + * Horde Date wrapper/logic class, including some calculation + * functions. + * + * @category Horde + * @package Date + */ +class Horde_Date_Utils +{ /** - * 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. + * Returns whether a year is 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. + * @param integer $year The year. * - * @return bool - * @access public - * @static + * @return boolean True if the year is a leap year. */ - function isValidDate($day, $month, $year) + public static function isLeapYear($year) { - if ($day < 1 || $month < 1 || $month > 12) + if (strlen($year) != 4 || preg_match('/\D/', $year)) { 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; } + + return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); } - - // }}} - // {{{ isLeapYearGregorian() - /** - * Returns true for a leap year, else false + * Returns the date of the year that corresponds to the first day of the + * given week. * - * 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 integer $week The week of the year to find the first day of. + * @param integer $year The year to calculate for. * - * @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 + * @return Horde_Date The date of the first day of the given week. */ - function isLeapYearGregorian($year = null) + public static function firstDayOfWeek($week, $year) { - if (is_null($year)) { - $year = Date_Calc::dateNow('%Y'); - } - return (($year % 4 == 0) && - ($year % 100 != 0)) || - ($year % 400 == 0); + return new Horde_Date(sprintf('%04dW%02d', $year, $week)); } - - // }}} - // {{{ isLeapYearJulian() - /** - * Returns true for a leap year, else false + * Returns the number of days in the specified month. * - * 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 integer $month The month + * @param integer $year The year. * - * @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 + * @return integer The number of days in the month. */ - function isLeapYearJulian($year = null) + public static function daysInMonth($month, $year) { - if (is_null($year)) { - $year = Date_Calc::dateNow('%Y'); + static $cache = array(); + if (!isset($cache[$year][$month])) { + $date = new DateTime(sprintf('%04d-%02d-01', $year, $month)); + $cache[$year][$month] = $date->format('t'); } - return $year % 4 == 0; + return $cache[$year][$month]; } - - // }}} - // {{{ isLeapYear() - /** - * Returns true for a leap year, else false + * Returns a relative, natural language representation of a timestamp * - * @param int $year the year. Use the complete year instead of the - * abbreviated version. E.g. use 2005, not 05. + * @todo Wider range of values ... maybe future time as well? + * @todo Support minimum resolution parameter. * - * @return boolean - * @access public - * @static + * @param mixed $time The time. Any format accepted by Horde_Date. + * @param string $date_format Format to display date if timestamp is + * more then 1 day old. + * @param string $time_format Format to display time if timestamp is 1 + * day old. + * + * @return string The relative time (i.e. 2 minutes ago) */ - function isLeapYear($year = null) + public static function relativeDateTime($time, $date_format = '%x', + $time_format = '%X') { - if (is_null($year)) { - $year = Date_Calc::dateNow('%Y'); + $date = new Horde_Date($time); + + $delta = time() - $date->timestamp(); + if ($delta < 60) { + return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta); } - if ($year < 1582) { - // pre Gregorio XIII - 1582 - return Date_Calc::isLeapYearJulian($year); - } else { - // post Gregorio XIII - 1582 - return Date_Calc::isLeapYearGregorian($year); + + $delta = round($delta / 60); + if ($delta < 60) { + return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta); } + + $delta = round($delta / 60); + if ($delta < 24) { + return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta); + } + + if ($delta > 24 && $delta < 48) { + $date = new Horde_Date($time); + return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format)); + } + + $delta = round($delta / 24); + if ($delta < 7) { + return sprintf(Horde_Date_Translation::t("%d days ago"), $delta); + } + + if (round($delta / 7) < 5) { + $delta = round($delta / 7); + return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta); + } + + // Default to the user specified date format. + return $date->strftime($date_format); } - - // }}} - // {{{ isFutureDate() - /** - * Determines if given date is a future date from now + * Tries to convert strftime() formatters to date() formatters. * - * @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. + * Unsupported formatters will be removed. * - * @return bool - * @access public - * @static + * @param string $format A strftime() formatting string. + * + * @return string A date() formatting string. */ - function isFutureDate($day, $month, $year) + public static function strftime2date($format) { - $this_year = Date_Calc::dateNow('%Y'); - $this_month = Date_Calc::dateNow('%m'); - $this_day = Date_Calc::dateNow('%d'); + $replace = array( + '/%a/' => 'D', + '/%A/' => 'l', + '/%d/' => 'd', + '/%e/' => 'j', + '/%j/' => 'z', + '/%u/' => 'N', + '/%w/' => 'w', + '/%U/' => '', + '/%V/' => 'W', + '/%W/' => '', + '/%b/' => 'M', + '/%B/' => 'F', + '/%h/' => 'M', + '/%m/' => 'm', + '/%C/' => '', + '/%g/' => '', + '/%G/' => 'o', + '/%y/' => 'y', + '/%Y/' => 'Y', + '/%H/' => 'H', + '/%I/' => 'h', + '/%i/' => 'g', + '/%M/' => 'i', + '/%p/' => 'A', + '/%P/' => 'a', + '/%r/' => 'h:i:s A', + '/%R/' => 'H:i', + '/%S/' => 's', + '/%T/' => 'H:i:s', + '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))', + '/%z/' => 'O', + '/%Z/' => '', + '/%c/' => '', + '/%D/' => 'm/d/y', + '/%F/' => 'Y-m-d', + '/%s/' => 'U', + '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))', + '/%n/' => "\n", + '/%t/' => "\t", + '/%%/' => '%' + ); - 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; + return preg_replace(array_keys($replace), array_values($replace), $format); } - - // }}} - // {{{ 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: - * - * DATE_PRECISION_YEAR - * DATE_PRECISION_MONTH - * DATE_PRECISION_DAY - * DATE_PRECISION_HOUR - * DATE_PRECISION_10MINUTES - * DATE_PRECISION_MINUTE - * DATE_PRECISION_10SECONDS - * DATE_PRECISION_SECOND - * - * 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: - * - * DATE_PRECISION_YEAR - 1 rounds the date to the nearest 10 - * years - * DATE_PRECISION_YEAR - 3 rounds the date to the nearest 1000 - * years - * DATE_PRECISION_SECOND + 1 rounds the date to 1 decimal - * point of a second - * DATE_PRECISION_SECOND + 1 rounds the date to 3 decimal - * points of a second - * DATE_PRECISION_SECOND + 1 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/) + * Copyright 2007-2012 Horde LLC (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. + * did not receive this file, see http://www.horde.org/licenses/lgpl21. * - * @since Horde 3.2 - * @package Horde_Date + * @category Horde + * @package 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, @@ -4416,39 +216,63 @@ define('HORDE_DATE_RECUR_YEARLY_WEEKDAY', 7); * Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date * object, an ISO time string or a hash. * - * @author Jan Schneider - * @since Horde 3.2 - * @package Horde_Date + * @author Jan Schneider + * @category Horde + * @package Date */ -class Horde_Date_Recurrence { +class Horde_Date_Recurrence +{ + /** No Recurrence **/ + const RECUR_NONE = 0; + + /** Recurs daily. */ + const RECUR_DAILY = 1; + + /** Recurs weekly. */ + const RECUR_WEEKLY = 2; + + /** Recurs monthly on the same date. */ + const RECUR_MONTHLY_DATE = 3; + + /** Recurs monthly on the same week day. */ + const RECUR_MONTHLY_WEEKDAY = 4; + + /** Recurs yearly on the same date. */ + const RECUR_YEARLY_DATE = 5; + + /** Recurs yearly on the same day of the year. */ + const RECUR_YEARLY_DAY = 6; + + /** Recurs yearly on the same week day. */ + const RECUR_YEARLY_WEEKDAY = 7; /** * The start time of the event. * * @var Horde_Date */ - var $start; + public $start; /** * The end date of the recurrence interval. * * @var Horde_Date */ - var $recurEnd = null; + public $recurEnd = null; /** * The number of recurrences. * * @var integer */ - var $recurCount = null; + public $recurCount = null; /** - * The type of recurrence this event follows. HORDE_DATE_RECUR_* constant. + * The type of recurrence this event follows. RECUR_* constant. * * @var integer */ - var $recurType = HORDE_DATE_RECUR_NONE; + public $recurType = self::RECUR_NONE; /** * The length of time between recurrences. The time unit depends on the @@ -4456,62 +280,62 @@ class Horde_Date_Recurrence { * * @var integer */ - var $recurInterval = 1; + public $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(); + public $recurData = null; /** * All the exceptions from recurrence for this event. * * @var array */ - var $exceptions = array(); + public $exceptions = array(); /** * All the dates this recurrence has been marked as completed. * * @var array */ - var $completions = array(); + public $completions = array(); /** * Constructor. * * @param Horde_Date $start Start of the recurring event. */ - function Horde_Date_Recurrence($start) + public function __construct($start) { $this->start = new Horde_Date($start); } + /** + * Resets the class properties. + */ + public function reset() + { + $this->recurEnd = null; + $this->recurCount = null; + $this->recurType = self::RECUR_NONE; + $this->recurInterval = 1; + $this->recurData = null; + $this->exceptions = array(); + $this->completions = array(); + } + /** * Checks if this event recurs on a given day of the week. * - * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* + * @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) + public function recurOnDay($dayMask) { return ($this->recurData & $dayMask); } @@ -4519,10 +343,10 @@ class Horde_Date_Recurrence { /** * Specifies the days this event recurs on. * - * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* + * @param integer $dayMask A mask consisting of Horde_Date::MASK_* * constants specifying the day(s) to recur on. */ - function setRecurOnDay($dayMask) + public function setRecurOnDay($dayMask) { $this->recurData = $dayMask; } @@ -4530,61 +354,23 @@ class Horde_Date_Recurrence { /** * Returns the days this event recurs on. * - * @return integer A mask consisting of HORDE_DATE_MASK_* constants + * @return integer A mask consisting of Horde_Date::MASK_* constants * specifying the day(s) this event recurs on. */ - function getRecurOnDays() + public 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 + * @param integer $recurrence RECUR_* constant of the * recurrence type to check for. * * @return boolean True if the event has the specified recurrence type. */ - function hasRecurType($recurrence) + public function hasRecurType($recurrence) { return ($recurrence == $this->recurType); } @@ -4592,9 +378,9 @@ class Horde_Date_Recurrence { /** * Sets a recurrence type for this event. * - * @param integer $recurrence A HORDE_DATE_RECUR_* constant. + * @param integer $recurrence A RECUR_* constant. */ - function setRecurType($recurrence) + public function setRecurType($recurrence) { $this->recurType = $recurrence; } @@ -4602,9 +388,9 @@ class Horde_Date_Recurrence { /** * Returns recurrence type of this event. * - * @return integer A HORDE_DATE_RECUR_* constant. + * @return integer A RECUR_* constant. */ - function getRecurType() + public function getRecurType() { return $this->recurType; } @@ -4614,17 +400,17 @@ class Horde_Date_Recurrence { * * @return string Human readable recurring type. */ - function getRecurName() + public 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"); + case self::RECUR_NONE: return Horde_Date_Translation::t("No recurrence"); + case self::RECUR_DAILY: return Horde_Date_Translation::t("Daily"); + case self::RECUR_WEEKLY: return Horde_Date_Translation::t("Weekly"); + case self::RECUR_MONTHLY_DATE: + case self::RECUR_MONTHLY_WEEKDAY: return Horde_Date_Translation::t("Monthly"); + case self::RECUR_YEARLY_DATE: + case self::RECUR_YEARLY_DAY: + case self::RECUR_YEARLY_WEEKDAY: return Horde_Date_Translation::t("Yearly"); } } @@ -4633,7 +419,7 @@ class Horde_Date_Recurrence { * * @param integer $interval The time between recurrences. */ - function setRecurInterval($interval) + public function setRecurInterval($interval) { if ($interval > 0) { $this->recurInterval = $interval; @@ -4645,7 +431,7 @@ class Horde_Date_Recurrence { * * @return integer The number of seconds between recurrences. */ - function getRecurInterval() + public function getRecurInterval() { return $this->recurInterval; } @@ -4655,7 +441,7 @@ class Horde_Date_Recurrence { * * @param integer $count The number of recurrences. */ - function setRecurCount($count) + public function setRecurCount($count) { if ($count > 0) { $this->recurCount = (int)$count; @@ -4671,7 +457,7 @@ class Horde_Date_Recurrence { * * @return integer The number recurrences. */ - function getRecurCount() + public function getRecurCount() { return $this->recurCount; } @@ -4681,7 +467,7 @@ class Horde_Date_Recurrence { * * @return boolean True if this recurrence has a fixed count. */ - function hasRecurCount() + public function hasRecurCount() { return isset($this->recurCount); } @@ -4691,9 +477,9 @@ class Horde_Date_Recurrence { * * @param Horde_Date $start The recurrence start. */ - function setRecurStart($start) + public function setRecurStart($start) { - $this->start = new Horde_Date($start); + $this->start = clone $start; } /** @@ -4701,7 +487,7 @@ class Horde_Date_Recurrence { * * @return Horde_Date The recurrence start. */ - function getRecurStart() + public function getRecurStart() { return $this->start; } @@ -4711,13 +497,15 @@ class Horde_Date_Recurrence { * * @param Horde_Date $end The recurrence end. */ - function setRecurEnd($end) + public function setRecurEnd($end) { if (!empty($end)) { // Recurrence counts and end dates are mutually exclusive. $this->recurCount = null; + $this->recurEnd = clone $end; + } else { + $this->recurEnd = $end; } - $this->recurEnd = new Horde_Date($end); } /** @@ -4725,7 +513,7 @@ class Horde_Date_Recurrence { * * @return Horde_Date The recurrence end. */ - function getRecurEnd() + public function getRecurEnd() { return $this->recurEnd; } @@ -4735,7 +523,7 @@ class Horde_Date_Recurrence { * * @return boolean True if this recurrence ends. */ - function hasRecurEnd() + public function hasRecurEnd() { return isset($this->recurEnd) && isset($this->recurEnd->year) && $this->recurEnd->year != 9999; @@ -4744,19 +532,24 @@ class Horde_Date_Recurrence { /** * Finds the next recurrence of this event that's after $afterDate. * - * @param Horde_Date $afterDate Return events after this date. + * @param Horde_Date|string $after 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) + public function nextRecurrence($after) { - $after = new Horde_Date($afterDate); - $after->correct(); + if (!($after instanceof Horde_Date)) { + $after = new Horde_Date($after); + } else { + $after = clone($after); + } + // Make sure $after and $this->start are in the same TZ + $after->setTimezone($this->start->timezone); if ($this->start->compareDateTime($after) >= 0) { - return new Horde_Date($this->start); + return clone $this->start; } if ($this->recurInterval == 0) { @@ -4764,38 +557,52 @@ class Horde_Date_Recurrence { } 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); + case self::RECUR_DAILY: + $diff = $this->start->diff($after); $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')); + $next = $this->start->add(array('day' => $recur)); if ((!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) && $next->compareDateTime($after) >= 0) { - return new Horde_Date($next); + return $next; } break; - case HORDE_DATE_RECUR_WEEKLY: + case self::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 = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'), + $this->start->year); + $start_week->timezone = $this->start->timezone; $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(); + $start_week->min = $this->start->min; + $start_week->sec = $this->start->sec; - $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, - $after_week->mday, $after_week->month, $after_week->year); + // Make sure we are not at the ISO-8601 first week of year while + // still in month 12...OR in the ISO-8601 last week of year while + // in month 1 and adjust the year accordingly. + $week = $after->format('W'); + if ($week == 1 && $after->month == 12) { + $theYear = $after->year + 1; + } elseif ($week >= 52 && $after->month == 1) { + $theYear = $after->year - 1; + } else { + $theYear = $after->year; + } + + $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear); + $after_week->timezone = $this->start->timezone; + $after_week_end = clone $after_week; + $after_week_end->mday += 7; + + $diff = $start_week->diff($after_week); $interval = $this->recurInterval * 7; $repeats = floor($diff / $interval); if ($diff % $interval < 7) { @@ -4819,13 +626,12 @@ class Horde_Date_Recurrence { * that lay between the start of the start week and the * recurrence start. */ - $next = new Horde_Date($start_week); + $next = clone $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; @@ -4840,9 +646,8 @@ class Horde_Date_Recurrence { } } - $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); + $next = clone $start_week; + $next->mday += $recur; while ($next->compareDateTime($after) < 0 && $next->compareDateTime($after_week_end) < 0) { if ($this->hasRecurCount() @@ -4851,7 +656,6 @@ class Horde_Date_Recurrence { $recurrences++; } ++$next->mday; - $next->correct(); } if ($this->hasRecurCount() && $recurrences >= $this->recurCount) { @@ -4865,7 +669,6 @@ class Horde_Date_Recurrence { 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) { @@ -4878,10 +681,12 @@ class Horde_Date_Recurrence { } break; - case HORDE_DATE_RECUR_MONTHLY_DATE: - $start = new Horde_Date($this->start); + case self::RECUR_MONTHLY_DATE: + $start = clone $this->start; if ($after->compareDateTime($start) < 0) { - $after = $start; + $after = clone $start; + } else { + $after = clone $after; } // If we're starting past this month's recurrence of the event, @@ -4889,7 +694,6 @@ class Horde_Date_Recurrence { if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; - $after->correct(); } // Adjust $start to be the first match. @@ -4909,10 +713,6 @@ class Horde_Date_Recurrence { 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) { @@ -4936,18 +736,13 @@ class Horde_Date_Recurrence { break; - case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: + case self::RECUR_MONTHLY_WEEKDAY: // Start with the start date of the event. - $estart = new Horde_Date($this->start); + $estart = clone $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(); - } + $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; @@ -4964,14 +759,9 @@ class Horde_Date_Recurrence { } $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); - } + $next = clone $estart; + $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. @@ -4990,9 +780,10 @@ class Horde_Date_Recurrence { return $next; - case HORDE_DATE_RECUR_YEARLY_DATE: + case self::RECUR_YEARLY_DATE: // Start with the start date of the event. - $estart = new Horde_Date($this->start); + $estart = clone $this->start; + $after = clone $after; if ($after->month > $estart->month || ($after->month == $estart->month && $after->mday > $estart->mday)) { @@ -5003,7 +794,7 @@ class Horde_Date_Recurrence { // Seperate case here for February 29th if ($estart->month == 2 && $estart->mday == 29) { - while (!Horde_Date::isLeapYear($after->year)) { + while (!Horde_Date_Utils::isLeapYear($after->year)) { ++$after->year; } } @@ -5027,7 +818,7 @@ class Horde_Date_Recurrence { return $estart; - case HORDE_DATE_RECUR_YEARLY_DAY: + case self::RECUR_YEARLY_DAY: // Check count first. $dayofyear = $this->start->dayOfYear(); $count = ($after->year - $this->start->year) / $this->recurInterval + 1; @@ -5039,18 +830,16 @@ class Horde_Date_Recurrence { } // Start with a rough interval. - $estart = new Horde_Date($this->start); + $estart = clone $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. @@ -5061,23 +850,13 @@ class Horde_Date_Recurrence { return $estart; - case HORDE_DATE_RECUR_YEARLY_WEEKDAY: + case self::RECUR_YEARLY_WEEKDAY: // Start with the start date of the event. - $estart = new Horde_Date($this->start); + $estart = clone $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]; - } + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; @@ -5093,14 +872,9 @@ class Horde_Date_Recurrence { } $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); - } + $next = clone $estart; + $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. @@ -5131,7 +905,7 @@ class Horde_Date_Recurrence { * * @return boolean True if an active recurrence exists. */ - function hasActiveRecurrence() + public function hasActiveRecurrence() { if (!$this->hasRecurEnd()) { return true; @@ -5144,12 +918,7 @@ class Horde_Date_Recurrence { 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)); + $next = $this->nextRecurrence($next->add(array('day' => 1))); } return false; @@ -5165,7 +934,7 @@ class Horde_Date_Recurrence { * has no active recurrence after * $afterDate. */ - function nextActiveRecurrence($afterDate) + public function nextActiveRecurrence($afterDate) { $next = $this->nextRecurrence($afterDate); while (is_object($next)) { @@ -5187,7 +956,7 @@ class Horde_Date_Recurrence { * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ - function addException($year, $month, $mday) + public function addException($year, $month, $mday) { $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } @@ -5199,7 +968,7 @@ class Horde_Date_Recurrence { * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ - function deleteException($year, $month, $mday) + public function deleteException($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions); if ($key !== false) { @@ -5216,7 +985,7 @@ class Horde_Date_Recurrence { * * @return boolean True if an exception exists for the given date. */ - function hasException($year, $month, $mday) + public function hasException($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getExceptions()); @@ -5228,7 +997,7 @@ class Horde_Date_Recurrence { * @return array Array containing the dates of all the exceptions in * YYYYMMDD form. */ - function getExceptions() + public function getExceptions() { return $this->exceptions; } @@ -5240,7 +1009,7 @@ class Horde_Date_Recurrence { * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ - function addCompletion($year, $month, $mday) + public function addCompletion($year, $month, $mday) { $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } @@ -5252,7 +1021,7 @@ class Horde_Date_Recurrence { * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ - function deleteCompletion($year, $month, $mday) + public function deleteCompletion($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions); if ($key !== false) { @@ -5269,7 +1038,7 @@ class Horde_Date_Recurrence { * * @return boolean True if a completion exists for the given date. */ - function hasCompletion($year, $month, $mday) + public function hasCompletion($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getCompletions()); @@ -5281,7 +1050,7 @@ class Horde_Date_Recurrence { * @return array Array containing the dates of all the completions in * YYYYMMDD form. */ - function getCompletions() + public function getCompletions() { return $this->completions; } @@ -5294,15 +1063,17 @@ class Horde_Date_Recurrence { * * @param string $rrule A vCalendar 1.0 conform RRULE value. */ - function fromRRule10($rrule) + public function fromRRule10($rrule) { + $this->reset(); + if (!$rrule) { return; } if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) { // No recurrence data - event does not recur. - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); } // Always default the recurInterval to 1. @@ -5312,19 +1083,21 @@ class Horde_Date_Recurrence { switch ($matches[1]) { case 'D': - $this->setRecurType(HORDE_DATE_RECUR_DAILY); + $this->setRecurType(self::RECUR_DAILY); break; case 'W': - $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); + $this->setRecurType(self::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); + $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]); @@ -5334,31 +1107,33 @@ class Horde_Date_Recurrence { $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); + $maskdays = array( + Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, + Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, + Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, + Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, + Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, + Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, + Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY, + ); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MP': - $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); break; case 'MD': - $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); + $this->setRecurType(self::RECUR_MONTHLY_DATE); break; case 'YM': - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); + $this->setRecurType(self::RECUR_YEARLY_DATE); break; case 'YD': - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); + $this->setRecurType(self::RECUR_YEARLY_DAY); break; } @@ -5384,49 +1159,51 @@ class Horde_Date_Recurrence { * @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. + * @param Horde_Icalendar $calendar A Horde_Icalendar object instance. * * @return string A vCalendar 1.0 conform RRULE value. */ - function toRRule10($calendar) + public function toRRule10($calendar) { switch ($this->recurType) { - case HORDE_DATE_RECUR_NONE: + case self::RECUR_NONE: return ''; - case HORDE_DATE_RECUR_DAILY: + case self::RECUR_DAILY: $rrule = 'D' . $this->recurInterval; break; - case HORDE_DATE_RECUR_WEEKLY: + case self::RECUR_WEEKLY: $rrule = 'W' . $this->recurInterval; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - for ($i = 0; $i <= 7 ; ++$i) { + for ($i = 0; $i <= 7; ++$i) { if ($this->recurOnDay(pow(2, $i))) { $rrule .= ' ' . $vcaldays[$i]; } } break; - case HORDE_DATE_RECUR_MONTHLY_DATE: + case self::RECUR_MONTHLY_DATE: $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); break; - case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: + case self::RECUR_MONTHLY_WEEKDAY: $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { - $nth_weekday++; + $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: + case self::RECUR_YEARLY_DATE: $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); break; - case HORDE_DATE_RECUR_YEARLY_DAY: + case self::RECUR_YEARLY_DAY: $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); break; @@ -5435,8 +1212,7 @@ class Horde_Date_Recurrence { } if ($this->hasRecurEnd()) { - $recurEnd = new Horde_Date($this->recurEnd); - $recurEnd->mday++; + $recurEnd = clone $this->recurEnd; return $rrule . ' ' . $calendar->_exportDateTime($recurEnd); } @@ -5452,8 +1228,10 @@ class Horde_Date_Recurrence { * * @param string $rrule An iCalendar 2.0 conform RRULE value. */ - function fromRRule20($rrule) + public function fromRRule20($rrule) { + $this->reset(); + // Parse the recurrence rule into keys and values. $rdata = array(); $parts = explode(';', $rrule); @@ -5466,22 +1244,23 @@ class Horde_Date_Recurrence { // 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); + $this->setRecurType(self::RECUR_DAILY); break; case 'WEEKLY': - $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); + $this->setRecurType(self::RECUR_WEEKLY); if (isset($rdata['BYDAY'])) { + $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, + ); $days = explode(',', $rdata['BYDAY']); $mask = 0; foreach ($days as $day) { @@ -5492,44 +1271,32 @@ class Horde_Date_Recurrence { // 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); + Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, + Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, + Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, + Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, + Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, + Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, + Horde_Date::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]); - } + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); } else { - $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); + $this->setRecurType(self::RECUR_MONTHLY_DATE); } break; case 'YEARLY': if (isset($rdata['BYYEARDAY'])) { - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); + $this->setRecurType(self::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); - } + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); } else { - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); + $this->setRecurType(self::RECUR_YEARLY_DATE); } break; } @@ -5539,14 +1306,17 @@ class Horde_Date_Recurrence { '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, - 'mday' => $mday))); + 'mday' => $mday, + 'hour' => 23, + 'min' => 59, + 'sec' => 59))); } if (isset($rdata['COUNT'])) { $this->setRecurCount($rdata['COUNT']); } } else { // No recurrence data - event does not recur. - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); } } @@ -5557,25 +1327,25 @@ class Horde_Date_Recurrence { * @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. + * @param Horde_Icalendar $calendar A Horde_Icalendar object instance. * * @return string An iCalendar 2.0 conform RRULE value. */ - function toRRule20($calendar) + public function toRRule20($calendar) { switch ($this->recurType) { - case HORDE_DATE_RECUR_NONE: + case self::RECUR_NONE: return ''; - case HORDE_DATE_RECUR_DAILY: + case self::RECUR_DAILY: $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; break; - case HORDE_DATE_RECUR_WEEKLY: + case self::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) { + for ($i = $flag = 0; $i <= 7; ++$i) { if ($this->recurOnDay(pow(2, $i))) { if ($flag) { $rrule .= ','; @@ -5586,59 +1356,45 @@ class Horde_Date_Recurrence { } break; - case HORDE_DATE_RECUR_MONTHLY_DATE: + case self::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++; - } + case self::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 = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; + . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; break; - case HORDE_DATE_RECUR_YEARLY_DATE: + case self::RECUR_YEARLY_DATE: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; break; - case HORDE_DATE_RECUR_YEARLY_DAY: + case self::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++; - } + case self::RECUR_YEARLY_WEEKDAY: + $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; + . $vcaldays[$this->start->dayOfWeek()] + . ';BYMONTH=' . $this->start->month; break; } if ($this->hasRecurEnd()) { - $recurEnd = new Horde_Date($this->recurEnd); - $recurEnd->mday++; + $recurEnd = clone $this->recurEnd; $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd); } if ($count = $this->getRecurCount()) { @@ -5654,30 +1410,16 @@ class Horde_Date_Recurrence { * * @return boolean True if the hash seemed valid, false otherwise. */ - function fromHash($hash) + public function fromHash($hash) { - if (!isset($hash['interval']) || !isset($hash['interval']) || - !isset($hash['range-type'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->reset(); + + if (!isset($hash['interval']) || !isset($hash['cycle'])) { + $this->setRecurType(self::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']); + $this->setRecurInterval((int)$hash['interval']); $parse_day = false; $set_daymask = false; @@ -5688,56 +1430,58 @@ class Horde_Date_Recurrence { switch ($hash['cycle']) { case 'daily': - $this->setRecurType(HORDE_DATE_RECUR_DAILY); + $this->setRecurType(self::RECUR_DAILY); break; case 'weekly': - $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); + $this->setRecurType(self::RECUR_WEEKLY); $parse_day = true; $set_daymask = true; break; case 'monthly': if (!isset($hash['daynumber'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); return false; } switch ($hash['type']) { case 'daynumber': - $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); + $this->setRecurType(self::RECUR_MONTHLY_DATE); $update_daynumber = true; break; case 'weekday': - $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); - $this->setRecurNthWeekday($hash['daynumber']); + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); + $nth_weekday = (int)$hash['daynumber']; + $hash['daynumber'] = 1; $parse_day = true; - $set_daymask = true; + $update_daynumber = true; + $update_weekday = true; break; } break; case 'yearly': if (!isset($hash['type'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); return false; } switch ($hash['type']) { case 'monthday': - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); + $this->setRecurType(self::RECUR_YEARLY_DATE); $update_month = true; $update_daynumber = true; break; case 'yearday': if (!isset($hash['month'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); return false; } - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); + $this->setRecurType(self::RECUR_YEARLY_DAY); // Start counting days in January. $hash['month'] = 'january'; $update_month = true; @@ -5746,67 +1490,63 @@ class Horde_Date_Recurrence { case 'weekday': if (!isset($hash['daynumber'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); return false; } - $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); - $this->setRecurNthWeekday($hash['daynumber']); + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); + $nth_weekday = (int)$hash['daynumber']; + $hash['daynumber'] = 1; $parse_day = true; - $set_daymask = true; - - if ($hash['month'] && isset($month2number[$hash['month']])) { - $this->setRecurByMonth($month2number[$hash['month']]); - } + $update_month = true; + $update_daynumber = true; + $update_weekday = true; break; } } - switch ($hash['range-type']) { - case 'number': - if (!isset($hash['range'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); - return false; + if (isset($hash['range-type']) && isset($hash['range'])) { + switch ($hash['range-type']) { + case 'number': + $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; } - - $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 ? $last_found_day = -1; if ($parse_day) { if (!isset($hash['day'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::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, + '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, + 'monday' => Horde_Date::DATE_MONDAY, + 'tuesday' => Horde_Date::DATE_TUESDAY, + 'wednesday' => Horde_Date::DATE_WEDNESDAY, + 'thursday' => Horde_Date::DATE_THURSDAY, + 'friday' => Horde_Date::DATE_FRIDAY, + 'saturday' => Horde_Date::DATE_SATURDAY, + 'sunday' => Horde_Date::DATE_SUNDAY, ); foreach ($hash['day'] as $day) { @@ -5826,6 +1566,21 @@ class Horde_Date_Recurrence { if ($update_month || $update_daynumber || $update_weekday) { if ($update_month) { + $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, + ); + if (isset($month2number[$hash['month']])) { $this->start->month = $month2number[$hash['month']]; } @@ -5833,7 +1588,7 @@ class Horde_Date_Recurrence { if ($update_daynumber) { if (!isset($hash['daynumber'])) { - $this->setRecurType(HORDE_DATE_RECUR_NONE); + $this->setRecurType(self::RECUR_NONE); return false; } @@ -5843,8 +1598,6 @@ class Horde_Date_Recurrence { if ($update_weekday) { $this->start->setNthWeekday($last_found_day, $nth_weekday); } - - $this->start->correct(); } // Exceptions. @@ -5864,9 +1617,9 @@ class Horde_Date_Recurrence { * * @return array The recurrence hash. */ - function toHash() + public function toHash() { - if ($this->getRecurType() == HORDE_DATE_RECUR_NONE) { + if ($this->getRecurType() == self::RECUR_NONE) { return array(); } @@ -5898,23 +1651,23 @@ class Horde_Date_Recurrence { $start = $this->getRecurStart(); switch ($this->getRecurType()) { - case HORDE_DATE_RECUR_DAILY: + case self::RECUR_DAILY: $hash['cycle'] = 'daily'; break; - case HORDE_DATE_RECUR_WEEKLY: + case self::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, + '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) { + foreach ($bits as $name => $bit) { if ($this->recurOnDay($bit)) { $days[] = $name; } @@ -5922,33 +1675,33 @@ class Horde_Date_Recurrence { $hash['day'] = $days; break; - case HORDE_DATE_RECUR_MONTHLY_DATE: + case self::RECUR_MONTHLY_DATE: $hash['cycle'] = 'monthly'; $hash['type'] = 'daynumber'; $hash['daynumber'] = $start->mday; break; - case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: + case self::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: + case self::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: + case self::RECUR_YEARLY_DAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'yearday'; $hash['daynumber'] = $start->dayOfYear(); break; - case HORDE_DATE_RECUR_YEARLY_WEEKDAY: + case self::RECUR_YEARLY_WEEKDAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'weekday'; $hash['daynumber'] = $start->weekOfMonth(); @@ -5975,4 +1728,44 @@ class Horde_Date_Recurrence { return $hash; } + /** + * Returns a simple object suitable for json transport representing this + * object. + * + * Possible properties are: + * - t: type + * - i: interval + * - e: end date + * - c: count + * - d: data + * - co: completions + * - ex: exceptions + * + * @return object A simple object. + */ + public function toJson() + { + $json = new stdClass; + $json->t = $this->recurType; + $json->i = $this->recurInterval; + if ($this->hasRecurEnd()) { + $json->e = $this->recurEnd->toJson(); + } + if ($this->recurCount) { + $json->c = $this->recurCount; + } + if ($this->recurData) { + $json->d = $this->recurData; + } + if ($this->completions) { + $json->co = $this->completions; + } + if ($this->exceptions) { + $json->ex = $this->exceptions; + } + return $json; + } + } + + diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php index db803efe..83ff1be4 100644 --- a/plugins/calendar/lib/calendar_ical.php +++ b/plugins/calendar/lib/calendar_ical.php @@ -144,22 +144,23 @@ class calendar_ical 'uid' => $ve->getAttributeDefault('UID'), 'changed' => $ve->getAttributeDefault('DTSTAMP', 0), 'title' => $ve->getAttributeDefault('SUMMARY'), - 'start' => $ve->getAttribute('DTSTART'), - 'end' => $ve->getAttribute('DTEND'), + 'start' => $this->_date2time($ve->getAttribute('DTSTART')), + 'end' => $this->_date2time($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']); + if (is_array($ve->getAttribute('DTSTART'))) $event['allday'] = true; - } - if (is_array($event['end'])) { - $event['end'] = $this->_date2time($event['end']) - 23 * 3600; - } + + if ($event['allday']) + $event['end']->sub(new DateInterval('PT23H')); + + // assign current timezone to event start/end + $event['start']->setTimezone($this->cal->timezone); + $event['end']->setTimezone($this->cal->timezone); // map other attributes to internal fields $_attendees = array(); @@ -215,7 +216,7 @@ class calendar_ical $params[$k] = $v; } if ($params['UNTIL']) - $params['UNTIL'] = $ve->_parseDateTime($params['UNTIL']); + $params['UNTIL'] = date_create($params['UNTIL']); if (!$params['INTERVAL']) $params['INTERVAL'] = 1; @@ -309,11 +310,11 @@ class calendar_ical 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(); - } + if (is_array($prop)) + return date_create(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone); + else if (is_numeric($prop)) + return date_create('@'.$prop); + return $prop; } @@ -353,15 +354,17 @@ class calendar_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; + $vevent .= $this->format_datetime("DTSTAMP", $event['changed'] ?: new DateTime(), false, true) . 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 + $event['end'] = clone $event['end']; + $event['end']->add(new DateInterval('P1D')); // ends the next day + $vevent .= $this->format_datetime("DTSTART", $event['start'], true) . self::EOL; + $vevent .= $this->format_datetime("DTEND", $event['end'], true) . self::EOL; } else { - $vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL; - $vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL; + $vevent .= $this->format_datetime("DTSTART", $event['start'], false) . self::EOL; + $vevent .= $this->format_datetime("DTEND", $event['end'], false) . self::EOL; } $vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL; $vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL; @@ -424,7 +427,26 @@ class calendar_ical // fold lines to 75 chars return rcube_vcard::rfc2425_fold($ical); } - + + private function format_datetime($attr, $dt, $dateonly = false, $utc = false) + { + if (is_numeric($dt)) + $dt = new DateTime('@'.$dt); + + if ($utc) + $dt->setTimezone(new DateTimeZone('UTC')); + + if ($dateonly) { + return $attr . ';VALUE=DATE:' . $dt->format('Ymd'); + } + else { + // ;TZID=Europe/Zurich:20120706T210000 + $tz = $dt->getTimezone(); + $tzid = $tz && $tz->getName() != 'UTC' ? ';TZID=' . $tz->getName() : ''; + return $attr . $tzid . ':' . $dt->format('Ymd\THis' . ($tzid ? '' : 'Z')); + } + } + private function escpape($str) { return preg_replace('/(? * @package @package_name@ * - * Copyright (C) 2011, Kolab Systems AG + * Copyright (C) 2012, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -28,9 +28,9 @@ class calendar_recurrence { private $cal; private $event; + private $next; private $engine; - private $tz_offset = 0; - private $dst_start = false; + private $duration; private $hour = 0; /** @@ -42,30 +42,30 @@ class calendar_recurrence function __construct($cal, $event) { $this->cal = $cal; + $this->event = $event; + $this->next = new Horde_Date($event['start'], $cal->timezone->getName()); + $this->hour = $this->next->hour; + + if (is_object($event['start']) && is_object($event['end'])) + $this->duration = $event['start']->diff($event['end']); // use Horde classes to compute recurring instances // TODO: replace with something that has less than 6'000 lines of code require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php'); - $this->event = $event; $this->engine = new Horde_Date_Recurrence($event['start']); $this->engine->fromRRule20(calendar::to_rrule($event['recurrence'])); if (is_array($event['recurrence']['EXDATE'])) { foreach ($event['recurrence']['EXDATE'] as $exdate) - $this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate)); + $this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j')); } - - $this->tz_offset = $event['allday'] ? $this->cal->gmt_offset - date('Z') : 0; - $this->next = new Horde_Date($event['start'] + $this->tz_offset); # shift all-day times to server timezone because computation operates in local TZ - $this->dst_start = $this->next->format('I'); - $this->hour = $this->next->hour; } /** - * Get timestamp of the next occurence of this event + * Get date/time of the next occurence of this event * - * @return mixed Unix timestamp or False if recurrence ended + * @return mixed DateTime object or False if recurrence ended */ public function next_start() { @@ -75,8 +75,8 @@ class calendar_recurrence $next->hour = $this->hour; # fix time for all-day events $next->min = 0; } - # $dst_diff = ($this->dst_start - $next->format('I')) * 3600; # consider difference in daylight saving between base event and recurring instance - $time = $next->timestamp() - $this->tz_offset; + + $time = $next->toDateTime(); $this->next = $next; } diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index b42a7de3..9615cffd 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -380,7 +380,7 @@ class calendar_ui $this->cal->gettext(array('name' => $label, 'vars' => array('min' => $n % 60, 'hrs' => intval($n / 60)))))); } - return html::tag('ul', $attrib, join("\n", $items), html::$common_attrib); + return html::tag('ul', $attrib + array('class' => 'toolbarmenu'), join("\n", $items), html::$common_attrib); } /** diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index bdd3f71a..04f858b3 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -29,7 +29,9 @@ body.calendarmain #mainscreen { } #datepicker { - margin-top: 12px; + position: absolute; + top: 40px; + left: 0; width: 100%; min-height: 190px; } @@ -89,7 +91,7 @@ body.calendarmain #mainscreen { position: absolute; left: 254px; width: 8px; - top: 37px; + top: 40px; bottom: 0; background: url(images/toggle.gif) 0 48% no-repeat transparent; cursor: pointer; @@ -241,17 +243,18 @@ pre { #calendartoolbar { position: absolute; top: -6px; - right: 0; + left: 0; height: 40px; - z-index: 200; } #calendartoolbar a { padding-right: 10px; } -#quicksearchbar { - right: 4px; +body.calendarmain #quicksearchbar { + top: -7px; + right: 2px; + z-index: 200; } body.calendarmain #searchmenulink { @@ -870,7 +873,6 @@ span.edit-alarm-set { } a.dropdown-link { - color: #CC0000; font-size: 12px; text-decoration: none; } @@ -904,7 +906,6 @@ a.dropdown-link:after { } .alarm-item div.alarm-actions a { - color: #CC0000; margin-right: 0.8em; text-decoration: none; } @@ -1019,7 +1020,7 @@ span.spacer { .fc-content { position: absolute !important; - top: 38px; + top: 40px; left: 0; right: 0; bottom: 28px; @@ -1210,7 +1211,9 @@ div.fc-event-location { background: -ms-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px ,#d6eaf3 100%); background: linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%); border: 0; - padding: 7px; + border-bottom: 1px solid #ccc; + height: 18px; + padding: 8px 7px 3px 7px; } .calendarmain .fc-view-table tr.fc-event td { diff --git a/plugins/calendar/skins/larry/templates/calendar.html b/plugins/calendar/skins/larry/templates/calendar.html index c072e352..ecc6e5f7 100644 --- a/plugins/calendar/skins/larry/templates/calendar.html +++ b/plugins/calendar/skins/larry/templates/calendar.html @@ -11,10 +11,11 @@
-
- - - +
+ + + +
@@ -30,12 +31,11 @@
- -
- - - - + +
+ + +
diff --git a/plugins/libkolab/lib/Horde_Date_Recurrence.php b/plugins/libkolab/lib/Horde_Date_Recurrence.php index bbdf13df..81f0857d 100644 --- a/plugins/libkolab/lib/Horde_Date_Recurrence.php +++ b/plugins/libkolab/lib/Horde_Date_Recurrence.php @@ -1198,7 +1198,10 @@ class Horde_Date_Recurrence '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, - 'mday' => $mday))); + 'mday' => $mday, + 'hour' => 23, + 'min' => 59, + 'sec' => 59))); } if (isset($rdata['COUNT'])) { $this->setRecurCount($rdata['COUNT']); diff --git a/plugins/libkolab/lib/kolab_date_recurrence.php b/plugins/libkolab/lib/kolab_date_recurrence.php index 55cc8c35..427f62ac 100644 --- a/plugins/libkolab/lib/kolab_date_recurrence.php +++ b/plugins/libkolab/lib/kolab_date_recurrence.php @@ -55,7 +55,7 @@ class kolab_date_recurrence $this->engine->fromRRule20($this->to_rrule($object['recurrence'])); // TODO: get that string directly from libkolabxml foreach ((array)$object['recurrence']['EXDATE'] as $exdate) - $this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate)); + $this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j')); $now = new DateTime('now', kolab_format::$timezone); $this->tz_offset = $object['allday'] ? $now->getOffset() - date('Z') : 0; @@ -124,7 +124,7 @@ class kolab_date_recurrence public function end($limit = 'now +1 year') { if ($this->object['recurrence']['UNTIL']) - return $this->object['recurrence']['UNTIL']; + return $this->object['recurrence']['UNTIL']->format('U'); $limit_time = strtotime($limit); while ($next_start = $this->next_start(true)) { @@ -154,11 +154,11 @@ class kolab_date_recurrence $k = strtoupper($k); switch ($k) { case 'UNTIL': - $val = gmdate('Ymd\THis', $val); + $val = $val->format('Ymd\THis'); break; case 'EXDATE': foreach ((array)$val as $i => $ex) - $val[$i] = gmdate('Ymd\THis', $ex); + $val[$i] = $ex->format('Ymd\THis'); $val = join(',', (array)$val); break; } diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php index 69b372c0..ffef059d 100644 --- a/plugins/libkolab/lib/kolab_format_contact.php +++ b/plugins/libkolab/lib/kolab_format_contact.php @@ -136,6 +136,7 @@ class kolab_format_contact extends kolab_format $this->obj->setUid($object['uid']); $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); // do the hard work of setting object values $nc = new NameComponents; diff --git a/plugins/libkolab/lib/kolab_format_distributionlist.php b/plugins/libkolab/lib/kolab_format_distributionlist.php index d060b575..a747b872 100644 --- a/plugins/libkolab/lib/kolab_format_distributionlist.php +++ b/plugins/libkolab/lib/kolab_format_distributionlist.php @@ -50,6 +50,7 @@ class kolab_format_distributionlist extends kolab_format $this->obj->setUid($object['uid']); $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); $this->obj->setName($object['name']); diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index 0f792ebf..33ed5af3 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -229,7 +229,7 @@ class kolab_format_event extends kolab_format_xcal if ($recurrence['range-type'] == 'number') $rrule['COUNT'] = intval($recurrence['range']); else if ($recurrence['range-type'] == 'date') - $rrule['UNTIL'] = $recurrence['range']; + $rrule['UNTIL'] = date_create('@'.$recurrence['range']); if ($recurrence['day']) { $byday = array(); @@ -251,7 +251,7 @@ class kolab_format_event extends kolab_format_xcal if ($recurrence['exclusion']) { foreach ((array)$recurrence['exclusion'] as $excl) - $rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date'])); // use time of event start + $rrule['EXDATE'][] = date_create($excl . date(' H:i:s', $rec['start-date'])); // use time of event start } } diff --git a/plugins/libkolab/lib/kolab_format_journal.php b/plugins/libkolab/lib/kolab_format_journal.php index 219c6f11..3be3813d 100644 --- a/plugins/libkolab/lib/kolab_format_journal.php +++ b/plugins/libkolab/lib/kolab_format_journal.php @@ -50,6 +50,7 @@ class kolab_format_journal extends kolab_format $this->obj->setUid($object['uid']); $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); // TODO: set object propeties diff --git a/plugins/libkolab/lib/kolab_format_note.php b/plugins/libkolab/lib/kolab_format_note.php index a5b2cd4f..9abf5f4d 100644 --- a/plugins/libkolab/lib/kolab_format_note.php +++ b/plugins/libkolab/lib/kolab_format_note.php @@ -50,6 +50,7 @@ class kolab_format_note extends kolab_format $this->obj->setUid($object['uid']); $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); // TODO: set object propeties diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php index 1cd803ee..f8247e0c 100644 --- a/plugins/libkolab/lib/kolab_format_xcal.php +++ b/plugins/libkolab/lib/kolab_format_xcal.php @@ -144,7 +144,7 @@ abstract class kolab_format_xcal extends kolab_format } else if ($until = self::php_datetime($rr->end())) { $until->setTime($object['start']->format('G'), $object['start']->format('i'), 0); - $object['recurrence']['UNTIL'] = $until->format('U'); + $object['recurrence']['UNTIL'] = $until; } if (($byday = $rr->byday()) && $byday->size()) { @@ -169,7 +169,7 @@ abstract class kolab_format_xcal extends kolab_format if ($exceptions = $this->obj->exceptionDates()) { for ($i=0; $i < $exceptions->size(); $i++) { if ($exdate = self::php_datetime($exceptions->get($i))) - $object['recurrence']['EXDATE'][] = $exdate->format('U'); + $object['recurrence']['EXDATE'][] = $exdate; } } } @@ -223,6 +223,7 @@ abstract class kolab_format_xcal extends kolab_format $this->obj->setUid($object['uid']); $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); // increment sequence $this->obj->setSequence($this->obj->sequence()+1); diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php index 80a9342a..0a50f7fe 100644 --- a/plugins/libkolab/lib/kolab_storage_cache.php +++ b/plugins/libkolab/lib/kolab_storage_cache.php @@ -515,7 +515,7 @@ class kolab_storage_cache // extend date range for recurring events if ($object['recurrence']) { $recurrence = new kolab_date_recurrence($object); - $sql_data['dtend'] = date('Y-m-d H:i:s', $recurrence->end() ?: strtotime('now +1 year')); + $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year')); } } else if ($objtype == 'task') {