Skip to content

Commit

Permalink
Enable pulling latest timezone data
Browse files Browse the repository at this point in the history
  • Loading branch information
kewisch committed Feb 12, 2022
1 parent 60bafe4 commit eae3325
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 29 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- name: "Linters"
run: grunt linters

- name: "grunt package-zones"
run: grunt package-zones

- name: "Node Unit Tests"
run: grunt unit-node-ci

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
node_modules
bower_components
*.pyc
tools/tzurl
tools/vzic
tools/tzdb
tools/libical
zoneinfo
api
Expand Down
16 changes: 13 additions & 3 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ module.exports = function(grunt) {
src: ['<%= libinfo.absfiles %>'],
dest: 'build/ical.js'
},
zones: {
src: ['build/ical.js', 'build/timezones.js'],
dest: 'build/ical.timezones.js'
},

validator: {
options: {
Expand Down Expand Up @@ -199,6 +203,11 @@ module.exports = function(grunt) {
files: {
'build/ical.min.js': ['build/ical.js']
}
},
zones: {
files: {
'build/ical.timezones.min.js': ['build/ical.timezones.js']
}
}
},
release: {
Expand Down Expand Up @@ -261,8 +270,10 @@ module.exports = function(grunt) {

grunt.loadTasks('tasks');

grunt.registerTask('default', ['package']);
grunt.registerTask('package', ['concat:dist', 'uglify']);
grunt.registerTask('default', ['package']); // You need to run package-zones on your own
grunt.registerTask('package', ['concat:dist', 'uglify:dist']);
grunt.registerTask('package-zones', ['package', 'tzdata', 'concat:zones', 'uglify:zones']);

grunt.registerTask('coverage', 'mocha_istanbul');
grunt.registerTask('linters', ['eslint']);
grunt.registerTask('test-browser', ['karma:unit', 'karma:acceptance']);
Expand All @@ -277,5 +288,4 @@ module.exports = function(grunt) {

// Additional tasks:
// - tests.js: performance-update, test-node
// - timezones.js: timezones
};
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ ICAL.js has no dependencies and uses fairly basic JavaScript. Therefore, it
should work in all versions of Node.js and modern browsers. It does use getters
and setters, so the minimum version of Internet Explorer is 9.

## Timezones
The stock ical.js does not register any timezones, due to the additional size it
brings. If you'd like to do timezone conversion, and the timezone definitions
are not included in the respective ics files, you'll need to use
`ical.timezones.js` or its minified counterpart.

## Documentation

For a few guides with code samples, please check out
Expand Down Expand Up @@ -155,7 +161,8 @@ output in the `api/` subdirectory.
### Packaging
When you are done with your work, you can run `grunt package` to create the
single-file build for use in the browser, including its minified counterpart
and the source map.
and the source map. Use `grunt package-zones` to also create a version of
ical.js that includes the latest timezone definitions.

## License
ical.js is licensed under the
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"files": [
"build/ical.js",
"build/ical.min.js",
"build/ical.timezones.js",
"build/ical.timezones.min.js",
"lib/ical/*.js"
],
"saucelabs": {
Expand Down
111 changes: 87 additions & 24 deletions tasks/timezones.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,104 @@

var util = require('util');
var path = require('path');
var which = require('which');
var child_process = require('child_process');

var OLSON_DB_REMOTE = 'http://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz';
var TZURL_DIR = process.env.TZURL_DIR || path.join(__dirname, '..', 'tools', 'tzurl')
var OLSON_DIR = process.env.OLSON_DIR || path.join(TZURL_DIR, 'olson');
var TZDB_DB_REMOTE = 'https://data.iana.org/time-zones/releases/tzdata%s.tar.gz';
var TZDB_VERSION_REMOTE = 'https://www.iana.org/time-zones';
var VZIC_REMOTE = "https://github.com/libical/vzic/archive/refs/heads/master.tar.gz";
var VZIC_EXEC = process.env.VZIC || which.sync('vzic', { nothrow: true });
var VZIC_DIR = process.env.VZIC_DIR || path.join(__dirname, '..', 'tools', 'vzic')
var TZDB_DIR = process.env.TZDB_DIR || path.join(__dirname, '..', 'tools', 'tzdb');
var TZDB_DIR_OUT = process.env.TZDB_DIR_OUT || path.join(TZDB_DIR, 'vzic-out');
var ICALJS_ZONES = process.env.VZIC_DIR || path.join(__dirname, '..', 'build', 'timezones.js');

module.exports = function(grunt) {
grunt.registerTask('timezones', 'Get Olson timezone data', function() {
var olsonversion = grunt.option('olsondb');
if (!olsonversion) {
olsonversion = (new Date()).getFullYear() + "a";
grunt.fail.warn('Need to specify --olsondb=<version>, e.g. ' + olsonversion);
return;
function processZone(zoneFile) {
// Smaller
let contents = grunt.file.read(zoneFile, { encoding: "utf-8" });
let lines = contents.split("\r\n");
let vtimezone = lines.slice(lines.indexOf("BEGIN:VTIMEZONE") + 1, lines.indexOf("END:VTIMEZONE")).join("\r\n");
return ` register(${JSON.stringify(vtimezone)});`;
}

function generateZonesFile(tzdbDir, tzdbVersion) {
let lines = [
`(function() {`,
` function register(tzdata) { ICAL.TimezoneService.register(ICAL.Component.fromString("BEGIN:VTIMEZONE\\r\\n" + tzdata + "END:VTIMEZONE")) };`,
` ICAL.TimezoneService.IANA_TZDB_VERSION = "${tzdbVersion}";`
];

let contents = grunt.file.read(path.join(tzdbDir, "zones.tab"), { encoding: "utf-8" });
for (let line of contents.split("\n")) {
let parts = line.split(" ");
if (parts.length == 3 && parts[2].length) {
lines.push(processZone(path.join(tzdbDir, parts[2] + ".ics")));
} else if (parts.length == 1 && parts[0].length) {
lines.push(processZone(path.join(tzdbDir, parts[0] + ".ics")));
}
}

if (grunt.file.isDir(TZURL_DIR)) {
grunt.log.ok('Using existing tzurl installation');
} else {
grunt.log.ok('Retrieving tzurl from svn');
child_process.execSync('svn export -r40 http://tzurl.googlecode.com/svn/trunk/ ' + TZURL_DIR);
lines.push("})();");

return lines.join("\n");
}

grunt.registerTask('tzdata', 'Get Olson timezone data', function() {
var tzdbversion = grunt.option('tzdb');
if (!tzdbversion) {
let page = child_process.execSync(`wget ${TZDB_VERSION_REMOTE} -O -`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf-8' });
let versionRE = page.match(/version">([0-9a-z]*)<\/span>/);
if (!versionRE) {
grunt.fail.error('Could not detect latest timezone database version');
return;
}
tzdbversion = versionRE[1];
grunt.log.ok('Latest tzdb version is ' + tzdbversion);
}

if (grunt.file.isDir(OLSON_DIR)) {
grunt.log.ok('Using olson database from ' + OLSON_DIR);
} else {
var url = util.format(OLSON_DB_REMOTE, olsonversion);
if (grunt.file.isDir(TZDB_DIR)) {
grunt.log.ok('Using tzdb database from ' + TZDB_DIR);
let existingVersion = grunt.file.read(path.join(TZDB_DIR, "version"), { encoding: "utf-8" }).trim();
if (existingVersion != tzdbversion) {
grunt.log.ok(`Existing version ${existingVersion} is outdated, upgrading to ${tzdbversion}`);
grunt.file.delete(TZDB_DIR);
}
}

if (!grunt.file.isDir(TZDB_DIR)) {
let url = util.format(TZDB_DB_REMOTE, tzdbversion);
grunt.log.ok('Downloading ' + url);
grunt.file.mkdir(OLSON_DIR);
child_process.execSync('wget ' + url + ' -O - | tar xz -C ' + OLSON_DIR);
grunt.file.mkdir(TZDB_DIR);
child_process.execSync(`wget ${url} -O - | tar xz`, { cwd: TZDB_DIR });
}

grunt.log.ok('Building tzurl tool');
child_process.execSync('make -C "' + TZURL_DIR + '" OLSON_DIR="' + OLSON_DIR + '"');
if (!VZIC_EXEC) {
if (grunt.file.isFile(path.join(VZIC_DIR, "vzic"))) {
grunt.log.ok('Using existing vzic installation from ' + VZIC_DIR);
} else {
grunt.log.ok('Retrieving vzic from github');
grunt.file.mkdir(VZIC_DIR);
child_process.execSync(`wget ${VZIC_REMOTE} -O - | tar xz --strip-components=1`, { cwd: VZIC_DIR });

grunt.log.ok('Building vzic tool');
child_process.execSync(
`make TZID_PREFIX="" OLSON_DIR="${TZDB_DIR}"`,
{ cwd: VZIC_DIR }
);
}

VZIC_EXEC = path.join(VZIC_DIR, "vzic");
}

if (grunt.file.isDir(TZDB_DIR_OUT)) {
grunt.log.ok("Using existing vzic data from " + TZDB_DIR_OUT);
} else {
grunt.log.ok('Running vzic');
child_process.execSync(VZIC_EXEC + ` --olson-dir ${TZDB_DIR} --output-dir ${TZDB_DIR_OUT}`);
}

grunt.log.ok('Running vzic');
child_process.execSync(path.join(TZURL_DIR, 'vzic'));
grunt.log.ok("Writing zones file " + ICALJS_ZONES);
grunt.file.write(ICALJS_ZONES, generateZonesFile(TZDB_DIR_OUT, tzdbversion), { encoding: "utf-8" });
});
};

0 comments on commit eae3325

Please sign in to comment.