Home | News | Projects | Releases
Bugs | RFE | Repositories | Help
Updated/Added copyright header and licensing to all source files
[xestiacalendar/.git] / source / objects / calendartimezone / CalendarTimezone.cpp
index 5ec9f3a..d699f13 100644 (file)
@@ -1,3 +1,21 @@
+// CalendarTimezone.cpp - CalendarTimezone class functions
+//
+// (c) 2016-2017 Xestia Software Development.
+//
+// This file is part of Xestia Calendar.
+//
+// Xestia Calendar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by the
+// Free Software Foundation, version 3 of the license.
+//
+// Xestia Calendar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with Xestia Calendar. If not, see <http://www.gnu.org/licenses/>
+
 #include "CalendarTimezone.h"
 
 using namespace std;
@@ -40,7 +58,7 @@ CalendarObjectValidResult CalendarTimezoneObject::ValidObject(){
        
        SeekCount = 0;
        
-       // Look for DTSTAMP.
+       // Look for TZID.
        
        for (vector<string>::iterator iter = ObjectName.begin();
                iter != ObjectName.end(); iter++){
@@ -150,4 +168,700 @@ void CalendarTimezoneObject::ProcessData(){
                
        }
        
+       // Process the data from TZURL.
+       
+       DataReceived = ProcessTextVectors(&ObjectName, &ObjectData, false, "TZURL");
+       
+       if (DataReceived.begin() != DataReceived.end()){
+       
+               try {
+                       TimeZoneURLTokens = DataReceived.begin()->first.substr(6);
+               }
+               
+               catch(const out_of_range &oor){
+                       // Do nothing as there is no data.
+               }               
+               
+               TimeZoneURLData = DataReceived.begin()->second;
+               
+       }
+       
+       // Process data from each STANDARD and DAYLIGHT.
+       
+       ProcessStandardDaylight();
+       
+       int TZSeekCount = 0;
+       int SeekCount = 0;
+       
+       for (vector<vector<string>>::iterator tzsiter = TimezoneStandardName.begin();
+               tzsiter != TimezoneStandardName.end(); tzsiter++){
+
+               bool DateTimeStartFound = false;
+               bool TimeZoneOffsetToFound = false;
+               bool TimeZoneOffsetFromFound = false;
+               
+               TimezoneDataStruct NewTZData;
+                                       
+               // Process the data from DTSTART.
+       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                               &TimezoneStandardData[SeekCount], false, "DTSTART");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.DateTimeStartTokens = DataReceived.begin()->first.substr(8);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.DateTimeStartData = DataReceived.begin()->second;
+                       DateTimeStartFound = true;
+               
+               }
+                       
+               // Process the data from TZOFFSETFROM.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                               &TimezoneStandardData[SeekCount], false, "TZOFFSETFROM");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.TimeZoneOffsetFromTokens = DataReceived.begin()->first.substr(13);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.TimeZoneOffsetFromData = DataReceived.begin()->second;
+                       TimeZoneOffsetFromFound = true;
+               
+               }
+                       
+               // Process the data from TZOFFSETTO.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                               &TimezoneStandardData[SeekCount], false, "TZOFFSETTO");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.TimeZoneOffsetToTokens = DataReceived.begin()->first.substr(11);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.TimeZoneOffsetToData = DataReceived.begin()->second;
+                       TimeZoneOffsetToFound = true;
+               
+               }
+               
+               // Process the data from RRULE.
+
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                       &TimezoneStandardData[SeekCount], false, "RRULE");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.RecurranceRuleDataTokens = DataReceived.begin()->first.substr(6);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }               
+                       
+                       NewTZData.RecurranceRuleData = DataReceived.begin()->second;
+               
+               }
+                       
+               // Process the data from COMMENT.
+       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                       &TimezoneStandardData[SeekCount], true, "COMMENT");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.CommentListTokens.push_back("");
+                       NewTZData.CommentListAltRep.push_back("");
+                       NewTZData.CommentListLanguage.push_back("");
+                       NewTZData.CommentList.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator propdataiter = PropertyData.begin();
+                               propdataiter != PropertyData.end(); propdataiter++){
+                       
+                               if (propdataiter->first == "ALTREP"){
+                               
+                                       NewTZData.CommentListAltRep[ObjectSeekCount] = propdataiter->second;
+                               
+                               } else if (propdataiter->first == "LANGUAGE"){
+                               
+                                       NewTZData.CommentListLanguage[ObjectSeekCount] = propdataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += propdataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += propdataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.CommentListTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.CommentList[ObjectSeekCount] = propiter->second;
+                       
+                       ObjectSeekCount++;
+               
+               }
+               
+               // Process the data from RDATE.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                       &TimezoneStandardData[SeekCount], true, "RDATE");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.RecurranceDateDataTokens.push_back("");
+                       NewTZData.RecurranceDateDataValue.push_back("");
+                       NewTZData.RecurranceDateDataTimeZoneParam.push_back("");
+                       NewTZData.RecurranceDateData.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator dataiter = PropertyData.begin();
+                               dataiter != PropertyData.end(); dataiter++){
+                       
+                               if (dataiter->first == "VALUE"){
+                       
+                                       NewTZData.RecurranceDateDataValue[ObjectSeekCount] = dataiter->second;
+                               
+                               } else if (dataiter->first == "TZID"){
+                               
+                                       NewTZData.RecurranceDateDataTimeZoneParam[ObjectSeekCount] = dataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += dataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += dataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.RecurranceDateDataTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.RecurranceDateData[ObjectSeekCount] = propiter->second;
+               
+                       ObjectSeekCount++;
+               
+               }
+                       
+               // Process the data from TZNAME.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneStandardName[SeekCount], 
+                       &TimezoneStandardData[SeekCount], true, "TZNAME");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.TimeZoneNameTokens.push_back("");
+                       NewTZData.TimeZoneNameLanguage.push_back("");
+                       NewTZData.TimeZoneNameData.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator dataiter = PropertyData.begin();
+                               dataiter != PropertyData.end(); dataiter++){
+                       
+                               if (dataiter->first == "LANGUAGE"){
+                               
+                                       NewTZData.TimeZoneNameLanguage[ObjectSeekCount] = dataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += dataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += dataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.TimeZoneNameTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.TimeZoneNameData[ObjectSeekCount] = propiter->second;
+               
+                       ObjectSeekCount++;
+               
+               }
+                       
+               ObjectSeekCount = 0;
+       
+               // Process data from X-*
+       
+               for(vector<string>::iterator propiter = TimezoneStandardName[SeekCount].begin(); 
+                       propiter != TimezoneStandardName[SeekCount].end(); ++propiter){
+               
+                       if (propiter->substr(0,2) == "X-" &&
+                               propiter->size() > 2){
+                                       
+                               NewTZData.XTokensData.push_back(TimezoneStandardData[SeekCount][ObjectSeekCount]);
+                               NewTZData.XTokensDataTokens.push_back(TimezoneStandardName[SeekCount][ObjectSeekCount]);
+                               
+                       }
+               
+                       ObjectSeekCount++;
+               
+               }
+               
+               // Check if the required values were given and
+               // insert NewTZData into the vector list of
+               // standard timezones.
+                       
+               if (DateTimeStartFound == true &&
+                       TimeZoneOffsetToFound == true &&
+                       TimeZoneOffsetFromFound == true){
+                                       
+                       TimezoneStandardCollection.push_back(NewTZData);
+                                       
+               }
+                       
+               SeekCount++;
+                       
+       }
+
+       TZSeekCount = 0;
+       SeekCount = 0;
+       
+       for (vector<vector<string>>::iterator tzsiter = TimezoneDaylightName.begin();
+               tzsiter != TimezoneDaylightName.end(); tzsiter++){
+
+               bool DateTimeStartFound = false;
+               bool TimeZoneOffsetToFound = false;
+               bool TimeZoneOffsetFromFound = false;
+               
+               TimezoneDataStruct NewTZData;
+                                       
+               // Process the data from DTSTART.
+       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                               &TimezoneDaylightData[SeekCount], false, "DTSTART");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.DateTimeStartTokens = DataReceived.begin()->first.substr(8);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.DateTimeStartData = DataReceived.begin()->second;
+                       DateTimeStartFound = true;
+               
+               }
+                       
+               // Process the data from TZOFFSETFROM.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                               &TimezoneDaylightData[SeekCount], false, "TZOFFSETFROM");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.TimeZoneOffsetFromTokens = DataReceived.begin()->first.substr(13);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.TimeZoneOffsetFromData = DataReceived.begin()->second;
+                       TimeZoneOffsetFromFound = true;
+               
+               }
+                       
+               // Process the data from TZOFFSETTO.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                               &TimezoneDaylightData[SeekCount], false, "TZOFFSETTO");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.TimeZoneOffsetToTokens = DataReceived.begin()->first.substr(11);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }
+               
+                       NewTZData.TimeZoneOffsetToData = DataReceived.begin()->second;
+                       TimeZoneOffsetToFound = true;
+               
+               }
+               
+               // Process the data from RRULE.
+
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                       &TimezoneDaylightData[SeekCount], false, "RRULE");
+       
+               if (DataReceived.begin() != DataReceived.end()){
+       
+                       try {
+                               NewTZData.RecurranceRuleDataTokens = DataReceived.begin()->first.substr(6);
+                       }
+               
+                       catch(const out_of_range &oor){
+                               // Do nothing as there is no data.
+                       }               
+                       
+                       NewTZData.RecurranceRuleData = DataReceived.begin()->second;
+               
+               }
+                       
+               // Process the data from COMMENT.
+       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                       &TimezoneDaylightData[SeekCount], true, "COMMENT");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.CommentListTokens.push_back("");
+                       NewTZData.CommentListAltRep.push_back("");
+                       NewTZData.CommentListLanguage.push_back("");
+                       NewTZData.CommentList.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator propdataiter = PropertyData.begin();
+                               propdataiter != PropertyData.end(); propdataiter++){
+                       
+                               if (propdataiter->first == "ALTREP"){
+                               
+                                       NewTZData.CommentListAltRep[ObjectSeekCount] = propdataiter->second;
+                               
+                               } else if (propdataiter->first == "LANGUAGE"){
+                               
+                                       NewTZData.CommentListLanguage[ObjectSeekCount] = propdataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += propdataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += propdataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.CommentListTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.CommentList[ObjectSeekCount] = propiter->second;
+                       
+                       ObjectSeekCount++;
+               
+               }
+               
+               // Process the data from RDATE.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                       &TimezoneDaylightData[SeekCount], true, "RDATE");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.RecurranceDateDataTokens.push_back("");
+                       NewTZData.RecurranceDateDataValue.push_back("");
+                       NewTZData.RecurranceDateDataTimeZoneParam.push_back("");
+                       NewTZData.RecurranceDateData.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator dataiter = PropertyData.begin();
+                               dataiter != PropertyData.end(); dataiter++){
+                       
+                               if (dataiter->first == "VALUE"){
+                       
+                                       NewTZData.RecurranceDateDataValue[ObjectSeekCount] = dataiter->second;
+                               
+                               } else if (dataiter->first == "TZID"){
+                               
+                                       NewTZData.RecurranceDateDataTimeZoneParam[ObjectSeekCount] = dataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += dataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += dataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.RecurranceDateDataTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.RecurranceDateData[ObjectSeekCount] = propiter->second;
+               
+                       ObjectSeekCount++;
+               
+               }
+                       
+               // Process the data from TZNAME.
+                       
+               DataReceived = ProcessTextVectors(&TimezoneDaylightName[SeekCount], 
+                       &TimezoneDaylightData[SeekCount], true, "TZNAME");
+
+               ObjectSeekCount = 0;
+       
+               for(multimap<string,string>::iterator propiter = DataReceived.begin(); 
+                       propiter != DataReceived.end(); 
+                       ++propiter){
+               
+                       NewTZData.TimeZoneNameTokens.push_back("");
+                       NewTZData.TimeZoneNameLanguage.push_back("");
+                       NewTZData.TimeZoneNameData.push_back("");
+                       
+                       bool TokenData = false;
+                       string PropertyTokens;
+               
+                       PropertyNameData = (string*)&propiter->first;
+               
+                       PropertyData = SplitValues(*PropertyNameData);
+                       
+                       for(map<string,string>::iterator dataiter = PropertyData.begin();
+                               dataiter != PropertyData.end(); dataiter++){
+                       
+                               if (dataiter->first == "LANGUAGE"){
+                               
+                                       NewTZData.TimeZoneNameLanguage[ObjectSeekCount] = dataiter->second;
+                               
+                               } else {
+                               
+                                       if (TokenData == false){
+                                               TokenData = true;
+                                       } else {
+                                               PropertyTokens += ";";
+                                       }
+                               
+                                       PropertyTokens += dataiter->first;
+                                       PropertyTokens += "=";
+                                       PropertyTokens += dataiter->second;
+                               
+                               }
+                               
+                       }
+               
+                       if (PropertyTokens.size() > 0){
+                               NewTZData.TimeZoneNameTokens[ObjectSeekCount] = PropertyTokens;
+                       }
+                       
+                       NewTZData.TimeZoneNameData[ObjectSeekCount] = propiter->second;
+               
+                       ObjectSeekCount++;
+               
+               }
+                       
+               ObjectSeekCount = 0;
+       
+               // Process data from X-*
+       
+               for(vector<string>::iterator propiter = TimezoneDaylightName[SeekCount].begin(); 
+                       propiter != TimezoneDaylightName[SeekCount].end(); ++propiter){
+               
+                       if (propiter->substr(0,2) == "X-" &&
+                               propiter->size() > 2){
+                                       
+                               NewTZData.XTokensData.push_back(TimezoneDaylightData[SeekCount][ObjectSeekCount]);
+                               NewTZData.XTokensDataTokens.push_back(TimezoneDaylightName[SeekCount][ObjectSeekCount]);
+                               
+                       }
+               
+                       ObjectSeekCount++;
+               
+               }
+               
+               // Check if the required values were given and
+               // insert NewTZData into the vector list of
+               // daylight timezones.
+                       
+               if (DateTimeStartFound == true &&
+                       TimeZoneOffsetToFound == true &&
+                       TimeZoneOffsetFromFound == true){
+                                       
+                       TimezoneDaylightCollection.push_back(NewTZData);
+                                       
+               }
+                       
+               SeekCount++;
+                       
+       }
+       
+}
+
+void CalendarTimezoneObject::ProcessStandardDaylight(){
+
+       int SeekCount = 0;
+       
+       bool TZMode = false; // False = STANDARD, True = DAYLIGHT.
+       bool ValidBegin = false;
+       vector<string> TimezoneObjectName;
+       vector<string> TimezoneObjectData;
+       
+       for (vector<string>::iterator iter = ObjectName.begin();
+               iter != ObjectName.end(); iter++){      
+       
+               // Check if the current name is BEGIN and
+               // data is either STANDARD or DAYLIGHT.
+                       
+               if (ObjectName[SeekCount] == "BEGIN" &&
+                       (ObjectData[SeekCount] == "STANDARD" || 
+                       ObjectData[SeekCount] == "DAYLIGHT")){
+                       
+                       if (ValidBegin == false){
+                               ValidBegin = true;
+                       } else {
+                               
+                       }
+                       
+                       if (ObjectData[SeekCount] == "STANDARD"){
+                               TZMode = false;
+                       } else if (ObjectData[SeekCount] == "DAYLIGHT") {
+                               TZMode = true;
+                       }
+                       
+                       SeekCount++;
+                       continue;
+                       
+               }
+               
+               // Check if current name is END and
+               // data is either STANDARD or DAYLIGHT.
+               
+               if (ObjectName[SeekCount] == "END" &&
+                       (ObjectData[SeekCount] == "STANDARD" || 
+                       ObjectData[SeekCount] == "DAYLIGHT") && 
+                       ValidBegin == true){
+                               
+                       if (TZMode == false && TimezoneObjectName.size() > 0){
+                               TimezoneStandardName.push_back(TimezoneObjectName);
+                               TimezoneStandardData.push_back(TimezoneObjectData);
+                       } else {
+                               TimezoneDaylightName.push_back(TimezoneObjectName);
+                               TimezoneDaylightData.push_back(TimezoneObjectData);
+                       }
+                       
+                       TimezoneObjectName.clear();
+                       TimezoneObjectData.clear();
+                       
+                       ValidBegin = false;
+                               
+               }
+               
+               if (ValidBegin == true){
+                       
+                       TimezoneObjectName.push_back(ObjectName[SeekCount]);
+                       TimezoneObjectData.push_back(ObjectData[SeekCount]);
+                       
+               }
+               
+               SeekCount++;
+                       
+       }
+                       
 }
\ No newline at end of file
Xestia Software Development
Yn Maystri
© 2006 - 2019 Xestia Software Development
Software

Xestia Address Book
Xestia Calendar
Development

Xestia Gelforn
Everything else

About
News
Privacy Policy