Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for MS timezone names, and support for TZ not UTC (said ToDo) #114

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
language: node_js
node_js:
- "8.9"
- "10.20"
install: npm install
196 changes: 153 additions & 43 deletions ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
}

}('ical', function(){

const moment=require('moment-timezone')
// Unescape Text re RFC 4.3.11
var text = function(t){
t = t || "";
Expand Down Expand Up @@ -90,71 +90,178 @@
var p = parseParams(params);

if (params && p){
//console.log("tz="+dt.tz+" TZID="+p.TZID)
dt.tz = p.TZID
if (dt.tz !== undefined)
{
// Remove surrouding quotes if found at the begining and at the end of the string
// (Occurs when parsing Microsoft Exchange events containing TZID with Windows standard format instead IANA)
dt.tz = dt.tz.replace(/^"(.*)"$/, "$1")
}
}

return dt
}

var dateParam = function(name){
return function (val, params, curr) {

var newDate = text(val);
let zoneTable=null
// Lookup IANA tz name from MS Names list
// if hash table not loaded, load it
function getIanaTZFromMS(msTZName){
if(!zoneTable){
const fs=require('fs')
const wtz=JSON.parse(fs.readFileSync(__dirname+"/windowsZones.json"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did not include the file windowsZones.json in your commit.

// get trhe list of MapZone objects
let v = getObjects(wtz,'name','mapZone')
// initializze the hash
zoneTable={}
// loop thru MS zones
for(let zone of v){
// get the object based on zone name
let wzone=zoneTable[zone.attributes.other]
// if not set
if(wzone==null)
// initialize
wzone={iana:[], type:zone.attributes.territory }
// loop thru the IANA names on this zone
for(let iana of zone.attributes.type.split(' ')){
// watch out for dups
if(wzone.iana.indexOf(iana)==-1)
// add to list
wzone.iana.push(iana)
}
// save the list
zoneTable[zone.attributes.other]=wzone
}
// fix for incomplete IANA timezone list. WEST shifts +1(winter) to +0 (summer)
//zoneTable['W. Europe Standard Time'].iana.unshift("Europe/London")
}
return zoneTable[msTZName].iana[0]
//return (msTZName =='W. Europe Standard Time')?"Europe/London":"America/Los_Angeles";
}

function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else
//if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
if (i == key && obj[i] == val || i == key && val == '') { //
objects.push(obj);
} else if (obj[i] == val && key == ''){
//only add if the object is not already in the array
if (objects.lastIndexOf(obj) == -1){
objects.push(obj);
}
}
}
return objects;
}

if (params && params[0] === "VALUE=DATE") {
// Just Date
const dateParam = function (name) {
return function (val, params, curr) {
let newDate = text(val);

var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
if (params && params.indexOf('VALUE=DATE') > -1 && params.indexOf('VALUE=DATE-TIME') === -1) {
// Just Date
// console.log(" date string="+val)
const comps = /^(\d{4})(\d{2})(\d{2}).*$/.exec(val);
if (comps !== null) {
// No TZ info - assume same timezone as this computer
newDate = new Date(
comps[1],
parseInt(comps[2], 10)-1,
comps[3]
);
newDate = new Date(comps[1], parseInt(comps[2], 10) - 1, comps[3]);

newDate = addTZ(newDate, params);
newDate.dateOnly = true;

// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
return storeValParam(name)(newDate, curr);
}
}


//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
// Typical RFC date-time format
const comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
newDate = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
// if timezone is Z == UTC
if (comps[7] === 'Z') {
// GMT
newDate = new Date(
Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10) - 1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
)
);
// add timezone if supplied
} else if (params && params[0] && params[0].indexOf('TZID=') > -1 && params[0].split('=')[1]) {
let tz = params[0].split('=')[1];
let found = '';
let offset = '';
// Remove quotes if found
tz = tz.replace(/^"(.*)"$/, '$1');
// Watch out for offset timezones
if (tz && tz.startsWith('(')) {
// Extract just the offset
const regex = /[+|-]\d*:\d*/;
offset = tz.match(regex);
tz = '';
found = offset;
}
// Watch out for windows timeszones, contain spaces
if (tz && !tz.startsWith('(') && tz.indexOf(' ') > -1) {
// get the IANA timezone needed for moment
const tz1 = getIanaTZFromMS(tz);
// if IANA tz found
if (tz1) {
// set
tz = tz1;
found = tz;
}
}
// if not previously validated IANA tz
if (found === '') {
// make sure in moment table
found = moment.tz.names().filter(zone => {
return zone === tz;
})[0];
}
// if IANA tz validated
if (found) {
newDate = moment.tz(val, 'YYYYMMDDTHHmmss' + offset, tz).toDate();
} else {
// Fallback if tz not found
// this will be local number, but 'assumed to be UTC'
newDate = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10) - 1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
} else {
// no tz supplied, so
// this will be local number, but 'assumed to be UTC'
newDate = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
parseInt(comps[1], 10),
parseInt(comps[2], 10) - 1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}

// add to date
newDate = addTZ(newDate, params);
}

}

// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
}
return storeValParam(name)(newDate, curr);
};
};


var geoParam = function(name){
Expand Down Expand Up @@ -436,8 +543,11 @@
l += lines[i+1].slice(1)
i += 1
}

var kv = l.split(":")
// remove any double quotes in any tzid statement
if(l.indexOf("TZID="))
l=l.replace(/"/g,"")
// Split on semicolons except if the semicolon is surrounded by quotes
var kv = l.split(/:(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/g)

if (kv.length < 2){
// Invalid line - must have k&v
Expand Down
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
"url": "git://github.com/peterbraden/ical.js.git"
},
"dependencies": {
"moment-timezone": "^0.5.31",
"request": "^2.88.0",
"rrule": "2.4.1"
"rrule": "2.4.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"vows": "0.8.2",
"underscore": "1.9.1"
},
"scripts": {
"test": "./node_modules/vows/bin/vows ./test/test.js"
"test": "./node_modules/vows/bin/vows ./test/test.js",
"postinstall": "./postinstall"
}
}
10 changes: 10 additions & 0 deletions postinstall
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
# get the json lookup file from xml file on npm install, only used if windows timezones are encountered
# set filename
fn=windowsZones.xml
# download unicode.org supported list of windows/iana timezone mappings
curl -sL https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/$fn >$fn
# convert to json
node node_modules/xml-js/bin/cli.js $fn >$(basename $fn .xml).json
# erase xml file
rm $fn
Loading