diff --git a/action/ajax.php b/action/ajax.php
--- a/action/ajax.php
+++ b/action/ajax.php
@@ -1,128 +1,129 @@
hlp =& plugin_load('helper','davcal');
}
function register(Doku_Event_Handler $controller) {
$controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown');
}
function handle_ajax_call_unknown(&$event, $param) {
if($event->data != 'plugin_davcal') return;
$event->preventDefault();
$event->stopPropagation();
global $INPUT;
$action = trim($INPUT->post->str('action'));
$id = trim($INPUT->post->str('id'));
$params = $INPUT->post->arr('params');
$user = $_SERVER['REMOTE_USER'];
$write = false;
$data = array();
$data['result'] = false;
$data['html'] = $this->getLang('unknown_error');
$acl = auth_quickaclcheck($id);
if($acl > AUTH_READ)
{
$write = true;
}
switch($action)
{
case 'newEvent':
if($write)
{
$data['result'] = true;
$data['html'] = $this->getLang('event_added');
$this->hlp->addCalendarEntryToCalendarForPage($id, $user, $params);
}
else
{
$data['result'] = false;
$data['html'] = $this->getLang('no_permission');
}
break;
case 'getEvents':
$startDate = $INPUT->post->str('start');
$endDate = $INPUT->post->str('end');
$data = $this->hlp->getEventsWithinDateRange($id, $user, $startDate, $endDate);
break;
case 'editEvent':
if($write)
{
$data['result'] = true;
$data['html'] = $this->getLang('event_edited');
$this->hlp->editCalendarEntryForPage($id, $user, $params);
}
else
{
$data['result'] = false;
$data['html'] = $this->getLang('no_permission');
}
break;
case 'deleteEvent':
if($write)
{
$data['result'] = true;
$data['html'] = $this->getLang('event_deleted');
$this->hlp->deleteCalendarEntryForPage($id, $params);
}
else
{
$data['result'] = false;
$data['html'] = $this->getLang('no_permission');
}
break;
case 'getSettings':
$data['result'] = true;
$data['settings'] = $this->hlp->getPersonalSettings($user);
$data['settings']['syncurl'] = $this->hlp->getSyncUrlForPage($id, $user);
+ $data['settings']['privateurl'] = $this->hlp->getPrivateURLForPage($id);
break;
case 'saveSettings':
$settings = array();
$settings['weeknumbers'] = $params['weeknumbers'];
$settings['timezone'] = $params['timezone'];
$settings['workweek'] = $params['workweek'];
if($this->hlp->savePersonalSettings($settings, $user))
{
$data['result'] = true;
$data['html'] = $this->getLang('settings_saved');
}
else
{
$data['result'] = false;
$data['html'] = $this->getLang('error_saving');
}
break;
}
// If we are still here, JSON output is requested
//json library of DokuWiki
require_once DOKU_INC . 'inc/JSON.php';
$json = new JSON();
//set content type
header('Content-Type: application/json');
echo $json->encode($data);
}
}
\ No newline at end of file
diff --git a/db/latest.version b/db/latest.version
--- a/db/latest.version
+++ b/db/latest.version
@@ -1,1 +1,1 @@
-3
+4
diff --git a/db/update0004.sql b/db/update0004.sql
new file mode 100644
--- /dev/null
+++ b/db/update0004.sql
@@ -0,0 +1,5 @@
+CREATE TABLE calendartoprivateurlmapping (
+ id integer primary key asc,
+ url text,
+ calid integer
+);
diff --git a/helper.php b/helper.php
--- a/helper.php
+++ b/helper.php
@@ -1,481 +1,554 @@
sqlite =& plugin_load('helper', 'sqlite');
if(!$this->sqlite)
{
msg('This plugin requires the sqlite plugin. Please install it.');
return;
}
if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/'))
{
return;
}
}
public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
if(is_null($userid))
$userid = $_SERVER['REMOTE_USER'];
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return $this->createCalendarForPage($name, $description, $id, $userid);
$query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ".
"description=".$this->sqlite->quote_string($description)." WHERE ".
"id=".$this->sqlite->quote_string($calid);
$res = $this->sqlite->query($query);
if($res !== false)
return true;
return false;
}
public function savePersonalSettings($settings, $userid = null)
{
if(is_null($userid))
$userid = $_SERVER['REMOTE_USER'];
$this->sqlite->query("BEGIN TRANSACTION");
$query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid);
$this->sqlite->query($query);
foreach($settings as $key => $value)
{
$query = "INSERT INTO calendarsettings (userid, key, value) VALUES (".
$this->sqlite->quote_string($userid).", ".
$this->sqlite->quote_string($key).", ".
$this->sqlite->quote_string($value).")";
$res = $this->sqlite->query($query);
if($res === false)
return false;
}
$this->sqlite->query("COMMIT TRANSACTION");
return true;
}
public function getPersonalSettings($userid = null)
{
if(is_null($userid))
$userid = $_SERVER['REMOTE_USER'];
// Some sane default settings
$settings = array(
'timezone' => 'local',
'weeknumbers' => '0',
'workweek' => '0'
);
$query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid);
$res = $this->sqlite->query($query);
$arr = $this->sqlite->res2arr($res);
foreach($arr as $row)
{
$settings[$row['key']] = $row['value'];
}
return $settings;
}
public function getCalendarIdForPage($id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
$query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id);
$res = $this->sqlite->query($query);
$row = $this->sqlite->res2row($res);
if(isset($row['calid']))
return $row['calid'];
else
return false;
}
public function getCalendarIdToPageMapping()
{
$query = "SELECT calid, page FROM pagetocalendarmapping";
$res = $this->sqlite->query($query);
$arr = $this->sqlite->res2arr($res);
return $arr;
}
public function getCalendarIdsForUser($principalUri)
{
$user = explode('/', $principalUri);
$user = end($user);
$mapping = $this->getCalendarIdToPageMapping();
$calids = array();
foreach($mapping as $row)
{
$id = $row['calid'];
$page = $row['page'];
$acl = auth_quickaclcheck($page);
if($acl >= AUTH_READ)
{
$write = $acl > AUTH_READ;
$calids[$id] = array('readonly' => !$write);
}
}
return $calids;
}
public function createCalendarForPage($name, $description, $id = null, $userid = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
if(is_null($userid))
$userid = $_SERVER['REMOTE_USER'];
$values = array('principals/'.$userid,
$name,
str_replace(array('/', ' ', ':'), '_', $id),
$description,
'VEVENT,VTODO',
0,
1);
$query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");";
$res = $this->sqlite->query($query);
if($res === false)
return false;
$query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ".
"displayname=".$this->sqlite->quote_string($values[1])." AND ".
"uri=".$this->sqlite->quote_string($values[2])." AND ".
"description=".$this->sqlite->quote_string($values[3]);
$res = $this->sqlite->query($query);
$row = $this->sqlite->res2row($res);
if(isset($row['id']))
{
$values = array($id, $row['id']);
$query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")";
$res = $this->sqlite->query($query);
return ($res !== false);
}
return false;
}
public function addCalendarEntryToCalendarForPage($id, $user, $params)
{
$settings = $this->getPersonalSettings($user);
if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
$timezone = new \DateTimeZone($settings['timezone']);
elseif($settings['timezone'] === 'local')
$timezone = new \DateTimeZone($params['detectedtz']);
else
$timezone = new \DateTimeZone('UTC');
$startDate = explode('-', $params['eventfrom']);
$startTime = explode(':', $params['eventfromtime']);
$endDate = explode('-', $params['eventto']);
$endTime = explode(':', $params['eventtotime']);
require_once('vendor/autoload.php');
$vcalendar = new \Sabre\VObject\Component\VCalendar();
$event = $vcalendar->add('VEVENT');
$uuid = \Sabre\VObject\UUIDUtil::getUUID();
$event->add('UID', $uuid);
$event->summary = $params['eventname'];
$description = $params['eventdescription'];
if($description !== '')
$event->add('DESCRIPTION', $description);
$dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
$event->add('DTSTAMP', $dtStamp);
$event->add('CREATED', $dtStamp);
$event->add('LAST-MODIFIED', $dtStamp);
$dtStart = new \DateTime();
$dtStart->setTimezone($timezone);
$dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
if($params['allday'] != '1')
$dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
$dtEnd = new \DateTime();
$dtEnd->setTimezone($timezone);
$dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
if($params['allday'] != '1')
$dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
// According to the VCal spec, we need to add a whole day here
if($params['allday'] == '1')
$dtEnd->add(new \DateInterval('P1D'));
$dtStartEv = $event->add('DTSTART', $dtStart);
$dtEndEv = $event->add('DTEND', $dtEnd);
if($params['allday'] == '1')
{
$dtStartEv['VALUE'] = 'DATE';
$dtEndEv['VALUE'] = 'DATE';
}
$calid = $this->getCalendarIdForPage($id);
$uri = uniqid('dokuwiki-').'.ics';
$now = new DateTime();
$eventStr = $vcalendar->serialize();
$values = array($calid,
$uri,
$eventStr,
$now->getTimestamp(),
'VEVENT',
$event->DTSTART->getDateTime()->getTimeStamp(),
$event->DTEND->getDateTime()->getTimeStamp(),
strlen($eventStr),
md5($eventStr),
uniqid()
);
$query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")";
$res = $this->sqlite->query($query);
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'added');
return true;
}
return false;
}
public function getCalendarSettings($calid)
{
$query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid);
$res = $this->sqlite->query($query);
$row = $this->sqlite->res2row($res);
return $row;
}
public function getEventsWithinDateRange($id, $user, $startDate, $endDate)
{
$settings = $this->getPersonalSettings($user);
if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
$timezone = new \DateTimeZone($settings['timezone']);
else
$timezone = new \DateTimeZone('UTC');
$data = array();
require_once('vendor/autoload.php');
$calid = $this->getCalendarIdForPage($id);
$startTs = new \DateTime($startDate);
$endTs = new \DateTime($endDate);
$query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE (calendarid=".
$this->sqlite->quote_string($calid)." AND firstoccurence > ".
$this->sqlite->quote_string($startTs->getTimestamp())." AND firstoccurence < ".
$this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=".
$this->sqlite->quote_string($calid)." AND lastoccurence > ".
$this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ".
$this->sqlite->quote_string($endTs->getTimestamp()).")";
$res = $this->sqlite->query($query);
$arr = $this->sqlite->res2arr($res);
foreach($arr as $row)
{
if(isset($row['calendardata']))
{
$entry = array();
$vcal = \Sabre\VObject\Reader::read($row['calendardata']);
$start = $vcal->VEVENT->DTSTART;
if($start !== null)
{
$dtStart = $start->getDateTime();
$dtStart->setTimezone($timezone);
$entry['start'] = $dtStart->format(\DateTime::ATOM);
if($start['VALUE'] == 'DATE')
$entry['allDay'] = true;
else
$entry['allDay'] = false;
}
$end = $vcal->VEVENT->DTEND;
if($end !== null)
{
$dtEnd = $end->getDateTime();
$dtEnd->setTimezone($timezone);
$entry['end'] = $dtEnd->format(\DateTime::ATOM);
}
$description = $vcal->VEVENT->DESCRIPTION;
if($description !== null)
$entry['description'] = (string)$description;
else
$entry['description'] = '';
$entry['title'] = (string)$vcal->VEVENT->summary;
$entry['id'] = $row['uid'];
$data[] = $entry;
}
}
return $data;
}
public function getEventWithUid($uid)
{
$query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=".
$this->sqlite->quote_string($uid);
$res = $this->sqlite->query($query);
$row = $this->sqlite->res2row($res);
return $row;
}
+ public function getAllCalendarEvents($calid)
+ {
+ $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=".
+ $this->sqlite->quote_string($calid);
+ $res = $this->sqlite->query($query);
+ $arr = $this->sqlite->res2arr($res);
+ return $arr;
+ }
+
public function editCalendarEntryForPage($id, $user, $params)
{
$settings = $this->getPersonalSettings($user);
if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
$timezone = new \DateTimeZone($settings['timezone']);
elseif($settings['timezone'] === 'local')
$timezone = new \DateTimeZone($params['detectedtz']);
else
$timezone = new \DateTimeZone('UTC');
$startDate = explode('-', $params['eventfrom']);
$startTime = explode(':', $params['eventfromtime']);
$endDate = explode('-', $params['eventto']);
$endTime = explode(':', $params['eventtotime']);
$uid = $params['uid'];
$event = $this->getEventWithUid($uid);
require_once('vendor/autoload.php');
if(!isset($event['calendardata']))
return false;
$uri = $event['uri'];
$calid = $event['calendarid'];
$vcal = \Sabre\VObject\Reader::read($event['calendardata']);
$vevent = $vcal->VEVENT;
$vevent->summary = $params['eventname'];
$dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
$description = $params['eventdescription'];
$vevent->remove('DESCRIPTION');
$vevent->remove('DTSTAMP');
$vevent->remove('LAST-MODIFIED');
$vevent->add('DTSTAMP', $dtStamp);
$vevent->add('LAST-MODIFIED', $dtStamp);
if($description !== '')
$vevent->add('DESCRIPTION', $description);
$dtStart = new \DateTime();
$dtStart->setTimezone($timezone);
$dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
if($params['allday'] != '1')
$dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
$dtEnd = new \DateTime();
$dtEnd->setTimezone($timezone);
$dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
if($params['allday'] != '1')
$dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
// According to the VCal spec, we need to add a whole day here
if($params['allday'] == '1')
$dtEnd->add(new \DateInterval('P1D'));
$vevent->remove('DTSTART');
$vevent->remove('DTEND');
$dtStartEv = $vevent->add('DTSTART', $dtStart);
$dtEndEv = $vevent->add('DTEND', $dtEnd);
if($params['allday'] == '1')
{
$dtStartEv['VALUE'] = 'DATE';
$dtEndEv['VALUE'] = 'DATE';
}
$now = new DateTime();
$eventStr = $vcal->serialize();
$query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr).
", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()).
", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()).
", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()).
", size=".strlen($eventStr).
", etag=".$this->sqlite->quote_string(md5($eventStr)).
" WHERE uid=".$this->sqlite->quote_string($uid);
$res = $this->sqlite->query($query);
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'modified');
return true;
}
return false;
}
public function deleteCalendarEntryForPage($id, $params)
{
$uid = $params['uid'];
$event = $this->getEventWithUid($uid);
$calid = $event['calendarid'];
$uri = $event['uri'];
$query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid);
$res = $this->sqlite->query($query);
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'deleted');
}
return true;
}
public function getSyncTokenForCalendar($calid)
{
$row = $this->getCalendarSettings($calid);
if(isset($row['synctoken']))
return $row['synctoken'];
return false;
}
public function operationNameToOperation($operationName)
{
switch($operationName)
{
case 'added':
return 1;
break;
case 'modified':
return 2;
break;
case 'deleted':
return 3;
break;
}
return false;
}
private function updateSyncTokenLog($calid, $uri, $operation)
{
$currentToken = $this->getSyncTokenForCalendar($calid);
$operationCode = $this->operationNameToOperation($operation);
if(($operationCode === false) || ($currentToken === false))
return false;
$values = array($uri,
$currentToken,
$calid,
$operationCode
);
$query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(".
$this->sqlite->quote_and_join($values, ',').")";
$res = $this->sqlite->query($query);
if($res === false)
return false;
$currentToken++;
$query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=".
$this->sqlite->quote_string($calid);
$res = $this->sqlite->query($query);
return ($res !== false);
}
public function getSyncUrlForPage($id, $user = null)
{
if(is_null($user))
$user = $_SERVER['REMOTE_USER'];
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return false;
$calsettings = $this->getCalendarSettings($calid);
if(!isset($calsettings['uri']))
return false;
$syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
return $syncurl;
}
+ public function getPrivateURLForPage($id)
+ {
+ $calid = $this->getCalendarIdForPage($id);
+ if($calid === false)
+ return false;
+
+ return $this->getPrivateURLForCalendar($calid);
+ }
+
+ public function getPrivateURLForCalendar($calid)
+ {
+ $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid);
+ $res = $this->sqlite->query($query);
+ $row = $this->sqlite->res2row($res);
+ if(!isset($row['url']))
+ {
+ $url = uniqid("dokuwiki-").".ics";
+ $values = array(
+ $url,
+ $calid
+ );
+ $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(".
+ $this->sqlite->quote_and_join($values, ", ").")";
+ $res = $this->sqlite->query($query);
+ if($res === false)
+ return false;
+ }
+ else
+ {
+ $url = $row['url'];
+ }
+ return DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$url;
+ }
+
+ public function getCalendarForPrivateURL($url)
+ {
+ $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url);
+ $res = $this->sqlite->query($query);
+ $row = $this->sqlite->res2row($res);
+ if(!isset($row['calid']))
+ return false;
+ return $row['calid'];
+ }
+
+ public function getCalendarAsICSFeed($calid)
+ {
+ $calSettings = $this->getCalendarSettings($calid);
+ if($calSettings === false)
+ return false;
+ $events = $this->getAllCalendarEvents($calid);
+ if($events === false)
+ return false;
+
+ $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:";
+ $out .= $calSettings['displayname']."\n";
+ foreach($events as $event)
+ {
+ $out .= rtrim($event['calendardata']);
+ $out .= "\n";
+ }
+ $out .= "END:VCALENDAR\n";
+ return $out;
+ }
+
}
diff --git a/ics.php b/ics.php
new file mode 100644
--- /dev/null
+++ b/ics.php
@@ -0,0 +1,22 @@
+getCalendarForPrivateURL($icsFile);
+
+if($calid === false)
+ die("No calendar with this name known.");
+
+$stream = $hlp->getCalendarAsICSFeed($calid);
+header("Content-Type: text/calendar");
+header("Content-Transfer-Encoding: Binary");
+header("Content-disposition: attachment; filename=\"calendar.ics\"");
+echo $stream;
\ No newline at end of file
diff --git a/jstz.js b/jstz.js
new file mode 100644
--- /dev/null
+++ b/jstz.js
@@ -0,0 +1,1313 @@
+(function (root) {/*global exports, Intl*/
+/**
+ * This script gives you the zone info key representing your device's time zone setting.
+ *
+ * @name jsTimezoneDetect
+ * @version 1.0.5
+ * @author Jon Nylander
+ * @license MIT License - https://bitbucket.org/pellepim/jstimezonedetect/src/default/LICENCE.txt
+ *
+ * For usage and examples, visit:
+ * http://pellepim.bitbucket.org/jstz/
+ *
+ * Copyright (c) Jon Nylander
+ */
+
+
+/**
+ * Namespace to hold all the code for timezone detection.
+ */
+var jstz = (function () {
+ 'use strict';
+ var HEMISPHERE_SOUTH = 's',
+
+ consts = {
+ DAY: 86400000,
+ HOUR: 3600000,
+ MINUTE: 60000,
+ SECOND: 1000,
+ BASELINE_YEAR: 2014,
+ MAX_SCORE: 864000000, // 10 days
+ AMBIGUITIES: {
+ 'America/Denver': ['America/Mazatlan'],
+ 'America/Chicago': ['America/Mexico_City'],
+ 'America/Santiago': ['America/Asuncion', 'America/Campo_Grande'],
+ 'America/Montevideo': ['America/Sao_Paulo'],
+ // Europe/Minsk should not be in this list... but Windows.
+ 'Asia/Beirut': ['Asia/Amman', 'Asia/Jerusalem', 'Europe/Helsinki', 'Asia/Damascus', 'Africa/Cairo', 'Asia/Gaza', 'Europe/Minsk'],
+ 'Pacific/Auckland': ['Pacific/Fiji'],
+ 'America/Los_Angeles': ['America/Santa_Isabel'],
+ 'America/New_York': ['America/Havana'],
+ 'America/Halifax': ['America/Goose_Bay'],
+ 'America/Godthab': ['America/Miquelon'],
+ 'Asia/Dubai': ['Asia/Yerevan'],
+ 'Asia/Jakarta': ['Asia/Krasnoyarsk'],
+ 'Asia/Shanghai': ['Asia/Irkutsk', 'Australia/Perth'],
+ 'Australia/Sydney': ['Australia/Lord_Howe'],
+ 'Asia/Tokyo': ['Asia/Yakutsk'],
+ 'Asia/Dhaka': ['Asia/Omsk'],
+ // In the real world Yerevan is not ambigous for Baku... but Windows.
+ 'Asia/Baku': ['Asia/Yerevan'],
+ 'Australia/Brisbane': ['Asia/Vladivostok'],
+ 'Pacific/Noumea': ['Asia/Vladivostok'],
+ 'Pacific/Majuro': ['Asia/Kamchatka', 'Pacific/Fiji'],
+ 'Pacific/Tongatapu': ['Pacific/Apia'],
+ 'Asia/Baghdad': ['Europe/Minsk', 'Europe/Moscow'],
+ 'Asia/Karachi': ['Asia/Yekaterinburg'],
+ 'Africa/Johannesburg': ['Asia/Gaza', 'Africa/Cairo']
+ }
+ },
+
+ /**
+ * Gets the offset in minutes from UTC for a certain date.
+ * @param {Date} date
+ * @returns {Number}
+ */
+ get_date_offset = function get_date_offset(date) {
+ var offset = -date.getTimezoneOffset();
+ return (offset !== null ? offset : 0);
+ },
+
+ /**
+ * This function does some basic calculations to create information about
+ * the user's timezone. It uses REFERENCE_YEAR as a solid year for which
+ * the script has been tested rather than depend on the year set by the
+ * client device.
+ *
+ * Returns a key that can be used to do lookups in jstz.olson.timezones.
+ * eg: "720,1,2".
+ *
+ * @returns {String}
+ */
+ lookup_key = function lookup_key() {
+ var january_offset = get_date_offset(new Date(consts.BASELINE_YEAR, 0, 2)),
+ june_offset = get_date_offset(new Date(consts.BASELINE_YEAR, 5, 2)),
+ diff = january_offset - june_offset;
+
+ if (diff < 0) {
+ return january_offset + ",1";
+ } else if (diff > 0) {
+ return june_offset + ",1," + HEMISPHERE_SOUTH;
+ }
+
+ return january_offset + ",0";
+ },
+
+
+ /**
+ * Tries to get the time zone key directly from the operating system for those
+ * environments that support the ECMAScript Internationalization API.
+ */
+ get_from_internationalization_api = function get_from_internationalization_api() {
+ if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
+ return;
+ }
+ var format = Intl.DateTimeFormat();
+ if (typeof format === "undefined" || typeof format.resolvedOptions === "undefined") {
+ return;
+ }
+ return format.resolvedOptions().timeZone;
+ },
+
+ /**
+ * Starting point for getting all the DST rules for a specific year
+ * for the current timezone (as described by the client system).
+ *
+ * Returns an object with start and end attributes, or false if no
+ * DST rules were found for the year.
+ *
+ * @param year
+ * @returns {Object} || {Boolean}
+ */
+ dst_dates = function dst_dates(year) {
+ var yearstart = new Date(year, 0, 1, 0, 0, 1, 0).getTime();
+ var yearend = new Date(year, 12, 31, 23, 59, 59).getTime();
+ var current = yearstart;
+ var offset = (new Date(current)).getTimezoneOffset();
+ var dst_start = null;
+ var dst_end = null;
+
+ while (current < yearend - 86400000) {
+ var dateToCheck = new Date(current);
+ var dateToCheckOffset = dateToCheck.getTimezoneOffset();
+
+ if (dateToCheckOffset !== offset) {
+ if (dateToCheckOffset < offset) {
+ dst_start = dateToCheck;
+ }
+ if (dateToCheckOffset > offset) {
+ dst_end = dateToCheck;
+ }
+ offset = dateToCheckOffset;
+ }
+
+ current += 86400000;
+ }
+
+ if (dst_start && dst_end) {
+ return {
+ s: find_dst_fold(dst_start).getTime(),
+ e: find_dst_fold(dst_end).getTime()
+ };
+ }
+
+ return false;
+ },
+
+ /**
+ * Probably completely unnecessary function that recursively finds the
+ * exact (to the second) time when a DST rule was changed.
+ *
+ * @param a_date - The candidate Date.
+ * @param padding - integer specifying the padding to allow around the candidate
+ * date for finding the fold.
+ * @param iterator - integer specifying how many milliseconds to iterate while
+ * searching for the fold.
+ *
+ * @returns {Date}
+ */
+ find_dst_fold = function find_dst_fold(a_date, padding, iterator) {
+ if (typeof padding === 'undefined') {
+ padding = consts.DAY;
+ iterator = consts.HOUR;
+ }
+
+ var date_start = new Date(a_date.getTime() - padding).getTime();
+ var date_end = a_date.getTime() + padding;
+ var offset = new Date(date_start).getTimezoneOffset();
+
+ var current = date_start;
+
+ var dst_change = null;
+ while (current < date_end - iterator) {
+ var dateToCheck = new Date(current);
+ var dateToCheckOffset = dateToCheck.getTimezoneOffset();
+
+ if (dateToCheckOffset !== offset) {
+ dst_change = dateToCheck;
+ break;
+ }
+ current += iterator;
+ }
+
+ if (padding === consts.DAY) {
+ return find_dst_fold(dst_change, consts.HOUR, consts.MINUTE);
+ }
+
+ if (padding === consts.HOUR) {
+ return find_dst_fold(dst_change, consts.MINUTE, consts.SECOND);
+ }
+
+ return dst_change;
+ },
+
+ windows7_adaptations = function windows7_adaptions(rule_list, preliminary_timezone, score, sample) {
+ if (score !== 'N/A') {
+ return score;
+ }
+ if (preliminary_timezone === 'Asia/Beirut') {
+ if (sample.name === 'Africa/Cairo') {
+ if (rule_list[6].s === 1398376800000 && rule_list[6].e === 1411678800000) {
+ return 0;
+ }
+ }
+ if (sample.name === 'Asia/Jerusalem') {
+ if (rule_list[6].s === 1395964800000 && rule_list[6].e === 1411858800000) {
+ return 0;
+ }
+ }
+ } else if (preliminary_timezone === 'America/Santiago') {
+ if (sample.name === 'America/Asuncion') {
+ if (rule_list[6].s === 1412481600000 && rule_list[6].e === 1397358000000) {
+ return 0;
+ }
+ }
+ if (sample.name === 'America/Campo_Grande') {
+ if (rule_list[6].s === 1413691200000 && rule_list[6].e === 1392519600000) {
+ return 0;
+ }
+ }
+ } else if (preliminary_timezone === 'America/Montevideo') {
+ if (sample.name === 'America/Sao_Paulo') {
+ if (rule_list[6].s === 1413687600000 && rule_list[6].e === 1392516000000) {
+ return 0;
+ }
+ }
+ } else if (preliminary_timezone === 'Pacific/Auckland') {
+ if (sample.name === 'Pacific/Fiji') {
+ if (rule_list[6].s === 1414245600000 && rule_list[6].e === 1396101600000) {
+ return 0;
+ }
+ }
+ }
+
+ return score;
+ },
+
+ /**
+ * Takes the DST rules for the current timezone, and proceeds to find matches
+ * in the jstz.olson.dst_rules.zones array.
+ *
+ * Compares samples to the current timezone on a scoring basis.
+ *
+ * Candidates are ruled immediately if either the candidate or the current zone
+ * has a DST rule where the other does not.
+ *
+ * Candidates are ruled out immediately if the current zone has a rule that is
+ * outside the DST scope of the candidate.
+ *
+ * Candidates are included for scoring if the current zones rules fall within the
+ * span of the samples rules.
+ *
+ * Low score is best, the score is calculated by summing up the differences in DST
+ * rules and if the consts.MAX_SCORE is overreached the candidate is ruled out.
+ *
+ * Yah follow? :)
+ *
+ * @param rule_list
+ * @param preliminary_timezone
+ * @returns {*}
+ */
+ best_dst_match = function best_dst_match(rule_list, preliminary_timezone) {
+ var score_sample = function score_sample(sample) {
+ var score = 0;
+
+ for (var j = 0; j < rule_list.length; j++) {
+
+ // Both sample and current time zone report DST during the year.
+ if (!!sample.rules[j] && !!rule_list[j]) {
+
+ // The current time zone's DST rules are inside the sample's. Include.
+ if (rule_list[j].s >= sample.rules[j].s && rule_list[j].e <= sample.rules[j].e) {
+ score = 0;
+ score += Math.abs(rule_list[j].s - sample.rules[j].s);
+ score += Math.abs(sample.rules[j].e - rule_list[j].e);
+
+ // The current time zone's DST rules are outside the sample's. Discard.
+ } else {
+ score = 'N/A';
+ break;
+ }
+
+ // The max score has been reached. Discard.
+ if (score > consts.MAX_SCORE) {
+ score = 'N/A';
+ break;
+ }
+ }
+ }
+
+ score = windows7_adaptations(rule_list, preliminary_timezone, score, sample);
+
+ return score;
+ };
+ var scoreboard = {};
+ var dst_zones = jstz.olson.dst_rules.zones;
+ var dst_zones_length = dst_zones.length;
+ var ambiguities = consts.AMBIGUITIES[preliminary_timezone];
+
+ for (var i = 0; i < dst_zones_length; i++) {
+ var sample = dst_zones[i];
+ var score = score_sample(dst_zones[i]);
+
+ if (score !== 'N/A') {
+ scoreboard[sample.name] = score;
+ }
+ }
+
+ for (var tz in scoreboard) {
+ if (scoreboard.hasOwnProperty(tz)) {
+ if (ambiguities.indexOf(tz) != -1) {
+ return tz;
+ }
+ }
+ }
+
+ return preliminary_timezone;
+ },
+
+ /**
+ * Takes the preliminary_timezone as detected by lookup_key().
+ *
+ * Builds up the current timezones DST rules for the years defined
+ * in the jstz.olson.dst_rules.years array.
+ *
+ * If there are no DST occurences for those years, immediately returns
+ * the preliminary timezone. Otherwise proceeds and tries to solve
+ * ambiguities.
+ *
+ * @param preliminary_timezone
+ * @returns {String} timezone_name
+ */
+ get_by_dst = function get_by_dst(preliminary_timezone) {
+ var get_rules = function get_rules() {
+ var rule_list = [];
+ for (var i = 0; i < jstz.olson.dst_rules.years.length; i++) {
+ var year_rules = dst_dates(jstz.olson.dst_rules.years[i]);
+ rule_list.push(year_rules);
+ }
+ return rule_list;
+ };
+ var check_has_dst = function check_has_dst(rules) {
+ for (var i = 0; i < rules.length; i++) {
+ if (rules[i] !== false) {
+ return true;
+ }
+ }
+ return false;
+ };
+ var rules = get_rules();
+ var has_dst = check_has_dst(rules);
+
+ if (has_dst) {
+ return best_dst_match(rules, preliminary_timezone);
+ }
+
+ return preliminary_timezone;
+ },
+
+ /**
+ * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary.
+ *
+ * Returns an object with one function ".name()"
+ *
+ * @returns Object
+ */
+ determine = function determine() {
+ var preliminary_tz = get_from_internationalization_api();
+
+ if (!preliminary_tz) {
+ preliminary_tz = jstz.olson.timezones[lookup_key()];
+
+ if (typeof consts.AMBIGUITIES[preliminary_tz] !== 'undefined') {
+ preliminary_tz = get_by_dst(preliminary_tz);
+ }
+ }
+
+ return {
+ name: function () {
+ return preliminary_tz;
+ }
+ };
+ };
+
+ return {
+ determine: determine
+ };
+}());
+
+
+jstz.olson = jstz.olson || {};
+
+/**
+ * The keys in this dictionary are comma separated as such:
+ *
+ * First the offset compared to UTC time in minutes.
+ *
+ * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it
+ * does.
+ *
+ * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere,
+ * only interesting for timezones with DST.
+ *
+ * The mapped arrays is used for constructing the jstz.TimeZone object from within
+ * jstz.determine();
+ */
+jstz.olson.timezones = {
+ '-720,0': 'Etc/GMT+12',
+ '-660,0': 'Pacific/Pago_Pago',
+ '-660,1,s': 'Pacific/Apia', // Why? Because windows... cry!
+ '-600,1': 'America/Adak',
+ '-600,0': 'Pacific/Honolulu',
+ '-570,0': 'Pacific/Marquesas',
+ '-540,0': 'Pacific/Gambier',
+ '-540,1': 'America/Anchorage',
+ '-480,1': 'America/Los_Angeles',
+ '-480,0': 'Pacific/Pitcairn',
+ '-420,0': 'America/Phoenix',
+ '-420,1': 'America/Denver',
+ '-360,0': 'America/Guatemala',
+ '-360,1': 'America/Chicago',
+ '-360,1,s': 'Pacific/Easter',
+ '-300,0': 'America/Bogota',
+ '-300,1': 'America/New_York',
+ '-270,0': 'America/Caracas',
+ '-240,1': 'America/Halifax',
+ '-240,0': 'America/Santo_Domingo',
+ '-240,1,s': 'America/Santiago',
+ '-210,1': 'America/St_Johns',
+ '-180,1': 'America/Godthab',
+ '-180,0': 'America/Argentina/Buenos_Aires',
+ '-180,1,s': 'America/Montevideo',
+ '-120,0': 'America/Noronha',
+ '-120,1': 'America/Noronha',
+ '-60,1': 'Atlantic/Azores',
+ '-60,0': 'Atlantic/Cape_Verde',
+ '0,0': 'UTC',
+ '0,1': 'Europe/London',
+ '60,1': 'Europe/Berlin',
+ '60,0': 'Africa/Lagos',
+ '60,1,s': 'Africa/Windhoek',
+ '120,1': 'Asia/Beirut',
+ '120,0': 'Africa/Johannesburg',
+ '180,0': 'Asia/Baghdad',
+ '180,1': 'Europe/Moscow',
+ '210,1': 'Asia/Tehran',
+ '240,0': 'Asia/Dubai',
+ '240,1': 'Asia/Baku',
+ '270,0': 'Asia/Kabul',
+ '300,1': 'Asia/Yekaterinburg',
+ '300,0': 'Asia/Karachi',
+ '330,0': 'Asia/Kolkata',
+ '345,0': 'Asia/Kathmandu',
+ '360,0': 'Asia/Dhaka',
+ '360,1': 'Asia/Omsk',
+ '390,0': 'Asia/Rangoon',
+ '420,1': 'Asia/Krasnoyarsk',
+ '420,0': 'Asia/Jakarta',
+ '480,0': 'Asia/Shanghai',
+ '480,1': 'Asia/Irkutsk',
+ '525,0': 'Australia/Eucla',
+ '525,1,s': 'Australia/Eucla',
+ '540,1': 'Asia/Yakutsk',
+ '540,0': 'Asia/Tokyo',
+ '570,0': 'Australia/Darwin',
+ '570,1,s': 'Australia/Adelaide',
+ '600,0': 'Australia/Brisbane',
+ '600,1': 'Asia/Vladivostok',
+ '600,1,s': 'Australia/Sydney',
+ '630,1,s': 'Australia/Lord_Howe',
+ '660,1': 'Asia/Kamchatka',
+ '660,0': 'Pacific/Noumea',
+ '690,0': 'Pacific/Norfolk',
+ '720,1,s': 'Pacific/Auckland',
+ '720,0': 'Pacific/Majuro',
+ '765,1,s': 'Pacific/Chatham',
+ '780,0': 'Pacific/Tongatapu',
+ '780,1,s': 'Pacific/Apia',
+ '840,0': 'Pacific/Kiritimati'
+};
+
+/* Build time: 2014-11-28 11:10:50Z Build by invoking python utilities/dst.py generate */
+jstz.olson.dst_rules = {
+ "years": [
+ 2008,
+ 2009,
+ 2010,
+ 2011,
+ 2012,
+ 2013,
+ 2014
+ ],
+ "zones": [
+ {
+ "name": "Africa/Cairo",
+ "rules": [
+ {
+ "e": 1219957200000,
+ "s": 1209074400000
+ },
+ {
+ "e": 1250802000000,
+ "s": 1240524000000
+ },
+ {
+ "e": 1285880400000,
+ "s": 1284069600000
+ },
+ false,
+ false,
+ false,
+ {
+ "e": 1411678800000,
+ "s": 1406844000000
+ }
+ ]
+ },
+ {
+ "name": "America/Asuncion",
+ "rules": [
+ {
+ "e": 1205031600000,
+ "s": 1224388800000
+ },
+ {
+ "e": 1236481200000,
+ "s": 1255838400000
+ },
+ {
+ "e": 1270954800000,
+ "s": 1286078400000
+ },
+ {
+ "e": 1302404400000,
+ "s": 1317528000000
+ },
+ {
+ "e": 1333854000000,
+ "s": 1349582400000
+ },
+ {
+ "e": 1364094000000,
+ "s": 1381032000000
+ },
+ {
+ "e": 1395543600000,
+ "s": 1412481600000
+ }
+ ]
+ },
+ {
+ "name": "America/Campo_Grande",
+ "rules": [
+ {
+ "e": 1203217200000,
+ "s": 1224388800000
+ },
+ {
+ "e": 1234666800000,
+ "s": 1255838400000
+ },
+ {
+ "e": 1266721200000,
+ "s": 1287288000000
+ },
+ {
+ "e": 1298170800000,
+ "s": 1318737600000
+ },
+ {
+ "e": 1330225200000,
+ "s": 1350792000000
+ },
+ {
+ "e": 1361070000000,
+ "s": 1382241600000
+ },
+ {
+ "e": 1392519600000,
+ "s": 1413691200000
+ }
+ ]
+ },
+ {
+ "name": "America/Goose_Bay",
+ "rules": [
+ {
+ "e": 1225594860000,
+ "s": 1205035260000
+ },
+ {
+ "e": 1257044460000,
+ "s": 1236484860000
+ },
+ {
+ "e": 1289098860000,
+ "s": 1268539260000
+ },
+ {
+ "e": 1320555600000,
+ "s": 1299988860000
+ },
+ {
+ "e": 1352005200000,
+ "s": 1331445600000
+ },
+ {
+ "e": 1383454800000,
+ "s": 1362895200000
+ },
+ {
+ "e": 1414904400000,
+ "s": 1394344800000
+ }
+ ]
+ },
+ {
+ "name": "America/Havana",
+ "rules": [
+ {
+ "e": 1224997200000,
+ "s": 1205643600000
+ },
+ {
+ "e": 1256446800000,
+ "s": 1236488400000
+ },
+ {
+ "e": 1288501200000,
+ "s": 1268542800000
+ },
+ {
+ "e": 1321160400000,
+ "s": 1300597200000
+ },
+ {
+ "e": 1352005200000,
+ "s": 1333256400000
+ },
+ {
+ "e": 1383454800000,
+ "s": 1362891600000
+ },
+ {
+ "e": 1414904400000,
+ "s": 1394341200000
+ }
+ ]
+ },
+ {
+ "name": "America/Mazatlan",
+ "rules": [
+ {
+ "e": 1225008000000,
+ "s": 1207472400000
+ },
+ {
+ "e": 1256457600000,
+ "s": 1238922000000
+ },
+ {
+ "e": 1288512000000,
+ "s": 1270371600000
+ },
+ {
+ "e": 1319961600000,
+ "s": 1301821200000
+ },
+ {
+ "e": 1351411200000,
+ "s": 1333270800000
+ },
+ {
+ "e": 1382860800000,
+ "s": 1365325200000
+ },
+ {
+ "e": 1414310400000,
+ "s": 1396774800000
+ }
+ ]
+ },
+ {
+ "name": "America/Mexico_City",
+ "rules": [
+ {
+ "e": 1225004400000,
+ "s": 1207468800000
+ },
+ {
+ "e": 1256454000000,
+ "s": 1238918400000
+ },
+ {
+ "e": 1288508400000,
+ "s": 1270368000000
+ },
+ {
+ "e": 1319958000000,
+ "s": 1301817600000
+ },
+ {
+ "e": 1351407600000,
+ "s": 1333267200000
+ },
+ {
+ "e": 1382857200000,
+ "s": 1365321600000
+ },
+ {
+ "e": 1414306800000,
+ "s": 1396771200000
+ }
+ ]
+ },
+ {
+ "name": "America/Miquelon",
+ "rules": [
+ {
+ "e": 1225598400000,
+ "s": 1205038800000
+ },
+ {
+ "e": 1257048000000,
+ "s": 1236488400000
+ },
+ {
+ "e": 1289102400000,
+ "s": 1268542800000
+ },
+ {
+ "e": 1320552000000,
+ "s": 1299992400000
+ },
+ {
+ "e": 1352001600000,
+ "s": 1331442000000
+ },
+ {
+ "e": 1383451200000,
+ "s": 1362891600000
+ },
+ {
+ "e": 1414900800000,
+ "s": 1394341200000
+ }
+ ]
+ },
+ {
+ "name": "America/Santa_Isabel",
+ "rules": [
+ {
+ "e": 1225011600000,
+ "s": 1207476000000
+ },
+ {
+ "e": 1256461200000,
+ "s": 1238925600000
+ },
+ {
+ "e": 1288515600000,
+ "s": 1270375200000
+ },
+ {
+ "e": 1319965200000,
+ "s": 1301824800000
+ },
+ {
+ "e": 1351414800000,
+ "s": 1333274400000
+ },
+ {
+ "e": 1382864400000,
+ "s": 1365328800000
+ },
+ {
+ "e": 1414314000000,
+ "s": 1396778400000
+ }
+ ]
+ },
+ {
+ "name": "America/Sao_Paulo",
+ "rules": [
+ {
+ "e": 1203213600000,
+ "s": 1224385200000
+ },
+ {
+ "e": 1234663200000,
+ "s": 1255834800000
+ },
+ {
+ "e": 1266717600000,
+ "s": 1287284400000
+ },
+ {
+ "e": 1298167200000,
+ "s": 1318734000000
+ },
+ {
+ "e": 1330221600000,
+ "s": 1350788400000
+ },
+ {
+ "e": 1361066400000,
+ "s": 1382238000000
+ },
+ {
+ "e": 1392516000000,
+ "s": 1413687600000
+ }
+ ]
+ },
+ {
+ "name": "Asia/Amman",
+ "rules": [
+ {
+ "e": 1225404000000,
+ "s": 1206655200000
+ },
+ {
+ "e": 1256853600000,
+ "s": 1238104800000
+ },
+ {
+ "e": 1288303200000,
+ "s": 1269554400000
+ },
+ {
+ "e": 1319752800000,
+ "s": 1301608800000
+ },
+ false,
+ false,
+ {
+ "e": 1414706400000,
+ "s": 1395957600000
+ }
+ ]
+ },
+ {
+ "name": "Asia/Damascus",
+ "rules": [
+ {
+ "e": 1225486800000,
+ "s": 1207260000000
+ },
+ {
+ "e": 1256850000000,
+ "s": 1238104800000
+ },
+ {
+ "e": 1288299600000,
+ "s": 1270159200000
+ },
+ {
+ "e": 1319749200000,
+ "s": 1301608800000
+ },
+ {
+ "e": 1351198800000,
+ "s": 1333058400000
+ },
+ {
+ "e": 1382648400000,
+ "s": 1364508000000
+ },
+ {
+ "e": 1414702800000,
+ "s": 1395957600000
+ }
+ ]
+ },
+ {
+ "name": "Asia/Dubai",
+ "rules": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Gaza",
+ "rules": [
+ {
+ "e": 1219957200000,
+ "s": 1206655200000
+ },
+ {
+ "e": 1252015200000,
+ "s": 1238104800000
+ },
+ {
+ "e": 1281474000000,
+ "s": 1269640860000
+ },
+ {
+ "e": 1312146000000,
+ "s": 1301608860000
+ },
+ {
+ "e": 1348178400000,
+ "s": 1333058400000
+ },
+ {
+ "e": 1380229200000,
+ "s": 1364508000000
+ },
+ {
+ "e": 1411678800000,
+ "s": 1395957600000
+ }
+ ]
+ },
+ {
+ "name": "Asia/Irkutsk",
+ "rules": [
+ {
+ "e": 1224957600000,
+ "s": 1206813600000
+ },
+ {
+ "e": 1256407200000,
+ "s": 1238263200000
+ },
+ {
+ "e": 1288461600000,
+ "s": 1269712800000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Jerusalem",
+ "rules": [
+ {
+ "e": 1223161200000,
+ "s": 1206662400000
+ },
+ {
+ "e": 1254006000000,
+ "s": 1238112000000
+ },
+ {
+ "e": 1284246000000,
+ "s": 1269561600000
+ },
+ {
+ "e": 1317510000000,
+ "s": 1301616000000
+ },
+ {
+ "e": 1348354800000,
+ "s": 1333065600000
+ },
+ {
+ "e": 1382828400000,
+ "s": 1364515200000
+ },
+ {
+ "e": 1414278000000,
+ "s": 1395964800000
+ }
+ ]
+ },
+ {
+ "name": "Asia/Kamchatka",
+ "rules": [
+ {
+ "e": 1224943200000,
+ "s": 1206799200000
+ },
+ {
+ "e": 1256392800000,
+ "s": 1238248800000
+ },
+ {
+ "e": 1288450800000,
+ "s": 1269698400000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Krasnoyarsk",
+ "rules": [
+ {
+ "e": 1224961200000,
+ "s": 1206817200000
+ },
+ {
+ "e": 1256410800000,
+ "s": 1238266800000
+ },
+ {
+ "e": 1288465200000,
+ "s": 1269716400000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Omsk",
+ "rules": [
+ {
+ "e": 1224964800000,
+ "s": 1206820800000
+ },
+ {
+ "e": 1256414400000,
+ "s": 1238270400000
+ },
+ {
+ "e": 1288468800000,
+ "s": 1269720000000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Vladivostok",
+ "rules": [
+ {
+ "e": 1224950400000,
+ "s": 1206806400000
+ },
+ {
+ "e": 1256400000000,
+ "s": 1238256000000
+ },
+ {
+ "e": 1288454400000,
+ "s": 1269705600000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Yakutsk",
+ "rules": [
+ {
+ "e": 1224954000000,
+ "s": 1206810000000
+ },
+ {
+ "e": 1256403600000,
+ "s": 1238259600000
+ },
+ {
+ "e": 1288458000000,
+ "s": 1269709200000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Yekaterinburg",
+ "rules": [
+ {
+ "e": 1224968400000,
+ "s": 1206824400000
+ },
+ {
+ "e": 1256418000000,
+ "s": 1238274000000
+ },
+ {
+ "e": 1288472400000,
+ "s": 1269723600000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Asia/Yerevan",
+ "rules": [
+ {
+ "e": 1224972000000,
+ "s": 1206828000000
+ },
+ {
+ "e": 1256421600000,
+ "s": 1238277600000
+ },
+ {
+ "e": 1288476000000,
+ "s": 1269727200000
+ },
+ {
+ "e": 1319925600000,
+ "s": 1301176800000
+ },
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Australia/Lord_Howe",
+ "rules": [
+ {
+ "e": 1207407600000,
+ "s": 1223134200000
+ },
+ {
+ "e": 1238857200000,
+ "s": 1254583800000
+ },
+ {
+ "e": 1270306800000,
+ "s": 1286033400000
+ },
+ {
+ "e": 1301756400000,
+ "s": 1317483000000
+ },
+ {
+ "e": 1333206000000,
+ "s": 1349537400000
+ },
+ {
+ "e": 1365260400000,
+ "s": 1380987000000
+ },
+ {
+ "e": 1396710000000,
+ "s": 1412436600000
+ }
+ ]
+ },
+ {
+ "name": "Australia/Perth",
+ "rules": [
+ {
+ "e": 1206813600000,
+ "s": 1224957600000
+ },
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Europe/Helsinki",
+ "rules": [
+ {
+ "e": 1224982800000,
+ "s": 1206838800000
+ },
+ {
+ "e": 1256432400000,
+ "s": 1238288400000
+ },
+ {
+ "e": 1288486800000,
+ "s": 1269738000000
+ },
+ {
+ "e": 1319936400000,
+ "s": 1301187600000
+ },
+ {
+ "e": 1351386000000,
+ "s": 1332637200000
+ },
+ {
+ "e": 1382835600000,
+ "s": 1364691600000
+ },
+ {
+ "e": 1414285200000,
+ "s": 1396141200000
+ }
+ ]
+ },
+ {
+ "name": "Europe/Minsk",
+ "rules": [
+ {
+ "e": 1224979200000,
+ "s": 1206835200000
+ },
+ {
+ "e": 1256428800000,
+ "s": 1238284800000
+ },
+ {
+ "e": 1288483200000,
+ "s": 1269734400000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Europe/Moscow",
+ "rules": [
+ {
+ "e": 1224975600000,
+ "s": 1206831600000
+ },
+ {
+ "e": 1256425200000,
+ "s": 1238281200000
+ },
+ {
+ "e": 1288479600000,
+ "s": 1269730800000
+ },
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ {
+ "name": "Pacific/Apia",
+ "rules": [
+ false,
+ false,
+ false,
+ {
+ "e": 1301752800000,
+ "s": 1316872800000
+ },
+ {
+ "e": 1333202400000,
+ "s": 1348927200000
+ },
+ {
+ "e": 1365256800000,
+ "s": 1380376800000
+ },
+ {
+ "e": 1396706400000,
+ "s": 1411826400000
+ }
+ ]
+ },
+ {
+ "name": "Pacific/Fiji",
+ "rules": [
+ false,
+ false,
+ {
+ "e": 1269698400000,
+ "s": 1287842400000
+ },
+ {
+ "e": 1327154400000,
+ "s": 1319292000000
+ },
+ {
+ "e": 1358604000000,
+ "s": 1350741600000
+ },
+ {
+ "e": 1390050000000,
+ "s": 1382796000000
+ },
+ {
+ "e": 1421503200000,
+ "s": 1414850400000
+ }
+ ]
+ }
+ ]
+}; if (typeof exports !== 'undefined') {
+ exports.jstz = jstz;
+ } else {
+ root.jstz = jstz;
+ }
+})(this);
diff --git a/lang/en/lang.php b/lang/en/lang.php
--- a/lang/en/lang.php
+++ b/lang/en/lang.php
@@ -1,39 +1,40 @@
');
jQuery.post(
DOKU_BASE + 'lib/exe/ajax.php',
{
call: 'plugin_davcal',
id: JSINFO.id,
action: 'saveSettings',
params: postArray
},
function(data)
{
var result = data['result'];
var html = data['html'];
jQuery('#dw_davcal__ajaxsettings').html(html);
if(result === true)
{
location.reload();
}
}
);
};
dialogButtons[LANG.plugins.davcal['cancel']] = function () {
dw_davcal__modals.hideSettingsDialog();
};
dw_davcal__modals.$settingsDialog = jQuery(document.createElement('div'))
.dialog({
autoOpen: false,
draggable: true,
title: LANG.plugins.davcal['settings'],
resizable: true,
buttons: dialogButtons,
})
.html(
'
' +
''
)
.parent()
.attr('id','dw_davcal__settings')
.show()
.appendTo('.dokuwiki:first');
jQuery('#dw_davcal__settings').position({
my: "center",
at: "center",
of: window
});
jQuery('#dw_davcal__settings_syncurl').on('click', function() {
jQuery(this).select();
});
+ jQuery('#dw_davcal__settings_privateurl').on('click', function() {
+ jQuery(this).select();
+ });
+
// attach event handlers
jQuery('#dw_davcal__settings .ui-dialog-titlebar-close').click(function(){
dw_davcal__modals.hideSettingsDialog();
});
var $tzdropdown = jQuery('#dw_davcal__settings_timezone');
jQuery('#fullCalendarTimezoneList option').each(function() {
jQuery('', {value: jQuery(this).val(),
text: jQuery(this).text()}).appendTo($tzdropdown);
});
if(dw_davcal__modals.settings)
{
if(dw_davcal__modals.settings['timezone'] !== '')
jQuery('#dw_davcal__settings_timezone').val(dw_davcal__modals.settings['timezone']);
if(dw_davcal__modals.settings['weeknumbers'] == 1)
jQuery('#dw_davcal__settings_weeknumbers').prop('checked', true);
else
jQuery('#dw_davcal__settings_weeknumbers').prop('checked', false);
if(dw_davcal__modals.settings['workweek'] == 1)
jQuery('#dw_davcal__settings_workweek').prop('checked', true);
else
jQuery('#dw_davcal__settings_workweek').prop('checked', false);
}
},
checkEvents : function() {
var allDay = jQuery('#dw_davcal__allday_edit').prop('checked');
var startDate = moment(jQuery('#dw_davcal__eventfrom_edit').val(), 'YYYY-MM-DD');
var endDate = moment(jQuery('#dw_davcal__eventto_edit').val(), 'YYYY-MM-DD');
if(!allDay)
{
var startTime = moment.duration(jQuery('#dw_davcal__eventfromtime_edit').val());
var endTime = moment.duration(jQuery('#dw_davcal__eventtotime_edit').val());
startDate.add(startTime);
endDate.add(endTime);
}
if(!startDate.isValid())
{
dw_davcal__modals.msg = LANG.plugins.davcal['start_date_invalid'];
dw_davcal__modals.showDialog(false);
return false;
}
if(!endDate.isValid())
{
dw_davcal__modals.msg = LANG.plugins.davcal['end_date_invalid'];
dw_davcal__modals.showDialog(false);
return false;
}
if(endDate.isBefore(startDate))
{
dw_davcal__modals.msg = LANG.plugins.davcal['end_date_before_start_date'];
dw_davcal__modals.showDialog(false);
return false;
}
if(!allDay && endDate.isSame(startDate))
{
dw_davcal__modals.msg = LANG.plugins.davcal['end_date_is_same_as_start_date'];
dw_davcal__modals.showDialog(false);
return false;
}
return true;
},
showEditEventDialog : function(event, edit) {
if(dw_davcal__modals.$editEventDialog)
return;
var title = '';
var dialogButtons = {};
var calEvent = [];
if(edit)
{
calEvent = event;
title = LANG.plugins.davcal['edit_event'];
dialogButtons[LANG.plugins.davcal['edit']] = function() {
if(!dw_davcal__modals.checkEvents())
return;
var postArray = { };
jQuery("input.dw_davcal__editevent, textarea.dw_davcal__editevent").each(function() {
if(jQuery(this).attr('type') == 'checkbox')
{
postArray[jQuery(this).prop('name')] = jQuery(this).prop('checked') ? 1 : 0;
}
else
{
postArray[jQuery(this).prop('name')] = jQuery(this).val();
}
});
jQuery('#dw_davcal__ajaxedit').html('');
jQuery.post(
DOKU_BASE + 'lib/exe/ajax.php',
{
call: 'plugin_davcal',
id: JSINFO.id,
action: 'editEvent',
params: postArray
},
function(data)
{
var result = data['result'];
var html = data['html'];
jQuery('#dw_davcal__ajaxedit').html(html);
if(result === true)
{
jQuery('#fullCalendar').fullCalendar('refetchEvents');
dw_davcal__modals.hideEditEventDialog();
}
}
);
};
dialogButtons[LANG.plugins.davcal['delete']] = function() {
dw_davcal__modals.action = 'deleteEvent';
dw_davcal__modals.msg = LANG.plugins.davcal['really_delete_this_event'];
dw_davcal__modals.completeCb = function(data) {
if(data.result == false)
{
dw_davcal__modals.msg = data.errmsg;
dw_davcal__modals.showDialog(false);
}
else
{
jQuery('#fullCalendar').fullCalendar('refetchEvents');
dw_davcal__modals.hideEditEventDialog();
}
};
dw_davcal__modals.showDialog(true);
};
}
else
{
calEvent.start = event;
calEvent.end = moment(event);
calEvent.start.hour(12);
calEvent.start.minute(0);
calEvent.end.hour(13);
calEvent.end.minute(0);
calEvent.allDay = false;
calEvent.title = '';
calEvent.description = '';
calEvent.id = '0';
title = LANG.plugins.davcal['create_new_event'];
dialogButtons[LANG.plugins.davcal['create']] = function() {
if(!dw_davcal__modals.checkEvents())
return;
var postArray = { };
jQuery("input.dw_davcal__editevent, textarea.dw_davcal__editevent").each(function() {
if(jQuery(this).attr('type') == 'checkbox')
{
postArray[jQuery(this).prop('name')] = jQuery(this).prop('checked') ? 1 : 0;
}
else
{
postArray[jQuery(this).prop('name')] = jQuery(this).val();
}
});
jQuery('#dw_davcal__ajaxedit').html('');
jQuery.post(
DOKU_BASE + 'lib/exe/ajax.php',
{
call: 'plugin_davcal',
id: JSINFO.id,
action: 'newEvent',
params: postArray
},
function(data)
{
var result = data['result'];
var html = data['html'];
jQuery('#dw_davcal__ajaxedit').html(html);
if(result === true)
{
jQuery('#fullCalendar').fullCalendar('refetchEvents');
dw_davcal__modals.hideEditEventDialog();
}
}
);
};
}
dialogButtons[LANG.plugins.davcal['cancel']] = function() {
dw_davcal__modals.hideEditEventDialog();
};
dw_davcal__modals.uid = calEvent.id;
dw_davcal__modals.$editEventDialog = jQuery(document.createElement('div'))
.dialog({
autoOpen: false,
draggable: true,
title: title,
resizable: true,
buttons: dialogButtons,
})
.html(
'' +
''
)
.parent()
.attr('id','dw_davcal__edit')
.show()
.appendTo('.dokuwiki:first');
jQuery('#dw_davcal__edit').position({
my: "center",
at: "center",
of: window
});
jQuery('#dw_davcal__tz_edit').val(dw_davcal__modals.detectedTz);
jQuery('#dw_davcal__uid_edit').val(calEvent.id);
jQuery('#dw_davcal__eventname_edit').val(calEvent.title);
jQuery('#dw_davcal__eventfrom_edit').val(calEvent.start.format('YYYY-MM-DD'));
jQuery('#dw_davcal__eventfromtime_edit').val(calEvent.start.format('HH:mm'));
jQuery('#dw_davcal__eventdescription_edit').val(calEvent.description);
if(calEvent.allDay && (calEvent.end === null))
{
jQuery('#dw_davcal__eventto_edit').val(calEvent.start.format('YYYY-MM-DD'));
jQuery('#dw_davcal__eventtotime_edit').val(calEvent.start.format('HH:mm'));
}
else if(calEvent.allDay)
{
endEvent = moment(calEvent.end);
endEvent.subtract(1, 'days');
jQuery('#dw_davcal__eventto_edit').val(endEvent.format('YYYY-MM-DD'));
jQuery('#dw_davcal__eventotime_edit').val(endEvent.format('HH:mm'));
}
else
{
jQuery('#dw_davcal__eventto_edit').val(calEvent.end.format('YYYY-MM-DD'));
jQuery('#dw_davcal__eventtotime_edit').val(calEvent.end.format('HH:mm'));
}
jQuery('#dw_davcal__allday_edit').prop('checked', calEvent.allDay);
// attach event handlers
jQuery('#dw_davcal__edit .ui-dialog-titlebar-close').click(function(){
dw_davcal__modals.hideEditEventDialog();
});
jQuery('#dw_davcal__eventfrom_edit').datetimepicker({format:'YYYY-MM-DD',
formatDate:'YYYY-MM-DD',
datepicker: true,
timepicker: false,
});
jQuery('#dw_davcal__eventfromtime_edit').datetimepicker({format:'HH:mm',
formatTime:'HH:mm',
datepicker: false,
timepicker: true,
step: 15});
jQuery('#dw_davcal__eventto_edit').datetimepicker({format:'YYYY-MM-DD',
formatDate:'YYYY-MM-DD',
datepicker: true,
timepicker: false,
});
jQuery('#dw_davcal__eventtotime_edit').datetimepicker({format:'HH:mm',
formatTime:'HH:mm',
datepicker: false,
timepicker: true,
step:15});
jQuery('#dw_davcal__allday_edit').change(function() {
if(jQuery(this).is(":checked"))
{
jQuery('#dw_davcal__eventfromtime_edit').prop('readonly', true);
jQuery('#dw_davcal__eventtotime_edit').prop('readonly', true);
}
else
{
jQuery('#dw_davcal__eventfromtime_edit').prop('readonly', false);
jQuery('#dw_davcal__eventtotime_edit').prop('readonly', false);
}
});
jQuery('#dw_davcal__allday_edit').change();
},
showDialog : function(confirm)
{
if(dw_davcal__modals.$confirmDialog)
return;
var dialogButtons = {};
var title = '';
if(confirm)
{
title = LANG.plugins.davcal['confirmation'];
dialogButtons[LANG.plugins.davcal['yes']] = function() {
jQuery.post(
DOKU_BASE + 'lib/exe/ajax.php',
{
call: 'plugin_davcal',
id: JSINFO.id,
action: dw_davcal__modals.action,
params: {
uid: dw_davcal__modals.uid
}
},
function(data)
{
dw_davcal__modals.completeCb(data);
}
);
dw_davcal__modals.hideDialog();
};
dialogButtons[LANG.plugins.tagrevisions['cancel']] = function() {
dw_davcal__modals.hideDialog();
};
}
else
{
title = LANG.plugins.davcal['info'];
dialogButtons[LANG.plugins.davcal['ok']] = function() {
dw_davcal__modals.hideDialog();
};
}
dw_davcal__modals.$dialog = jQuery(document.createElement('div'))
.dialog({
autoOpen: false,
draggable: true,
title: title,
resizable: true,
buttons: dialogButtons,
})
.html(
'' + dw_davcal__modals.msg + '
'
)
.parent()
.attr('id','dw_davcal__confirm')
.show()
.appendTo('.dokuwiki:first');
jQuery('#dw_davcal__confirm').position({
my: "center",
at: "center",
of: window
});
// attach event handlers
jQuery('#dw_davcal__confirm .ui-dialog-titlebar-close').click(function(){
dw_davcal__modals.hideDialog();
});
},
hideEditEventDialog : function() {
dw_davcal__modals.$editEventDialog.empty();
dw_davcal__modals.$editEventDialog.remove();
dw_davcal__modals.$editEventDialog = null;
},
hideDialog: function() {
dw_davcal__modals.$dialog.empty();
dw_davcal__modals.$dialog.remove();
dw_davcal__modals.$dialog = null;
},
hideSettingsDialog: function() {
dw_davcal__modals.$settingsDialog.empty();
dw_davcal__modals.$settingsDialog.remove();
dw_davcal__modals.$settingsDialog = null;
}
};