Bug 1494160 - Modify timezone update script so that zones.json can contain recent and future rules; r=Fallen

This commit is contained in:
Geoff Lankow 2019-05-20 15:53:09 +12:00
Родитель 0b95f178ee
Коммит 2308ad78d9
5 изменённых файлов: 317 добавлений и 70 удалений

Просмотреть файл

@ -480,3 +480,6 @@ pref.timezone.Asia.Atyrau=Asia/Atyrau
#added with 2.2017b #added with 2.2017b
pref.timezone.America.Punta_Arenas=America/Punta Arenas pref.timezone.America.Punta_Arenas=America/Punta Arenas
#added with 2.2018i
pref.timezone.Asia.Qostanay=Asia/Qostanay

Просмотреть файл

@ -0,0 +1,117 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const FEBRUARY = 1;
const OCTOBER = 9;
const NOVEMBER = 10;
const UTC_MINUS_3 = -3 * 3600;
const UTC_MINUS_2 = -2 * 3600;
function run_test() {
do_calendar_startup(run_next_test);
}
// This test requires timezone data going back to 2016. It's been kept here as an example.
/* add_test(function testCaracas() {
let time = cal.createDateTime();
let zone = cal.getTimezoneService().getTimezone("America/Caracas");
for (let month = JANUARY; month <= DECEMBER; month++) {
time.resetTo(2015, month, 1, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_430, time.toString());
}
for (let month = JANUARY; month <= APRIL; month++) {
time.resetTo(2016, month, 1, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_430, time.toString());
}
time.resetTo(2016, MAY, 1, 1, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_430, time.toString());
time.resetTo(2016, MAY, 1, 3, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_4, time.toString());
for (let month = JUNE; month <= DECEMBER; month++) {
time.resetTo(2016, month, 1, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_4, time.toString());
}
for (let month = JANUARY; month <= DECEMBER; month++) {
time.resetTo(2017, month, 1, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_4, time.toString());
}
run_next_test();
}); */
// Brazil's rules are complicated. This tests every change in the time range we have data for.
add_test(function testSaoPaulo() {
let time = cal.createDateTime();
let zone = cal.getTimezoneService().getTimezone("America/Sao_Paulo");
time.resetTo(2018, FEBRUARY, 17, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2018, FEBRUARY, 18, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2018, NOVEMBER, 3, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2018, NOVEMBER, 4, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2019, FEBRUARY, 16, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2019, FEBRUARY, 17, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2019, NOVEMBER, 2, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2019, NOVEMBER, 3, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2020, FEBRUARY, 15, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2020, FEBRUARY, 16, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2020, OCTOBER, 31, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2020, NOVEMBER, 1, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2021, FEBRUARY, 20, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2021, FEBRUARY, 21, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2021, NOVEMBER, 6, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2021, NOVEMBER, 7, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2022, FEBRUARY, 19, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
time.resetTo(2022, FEBRUARY, 20, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2022, NOVEMBER, 5, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_3, time.toString());
time.resetTo(2022, NOVEMBER, 6, 0, 0, 0, zone);
equal(time.timezoneOffset, UTC_MINUS_2, time.toString());
run_next_test();
});

Просмотреть файл

@ -49,6 +49,7 @@ skip-if = true # See bug 1481180. requesttimeoutfactor = 2
[test_startup_service.js] [test_startup_service.js]
[test_storage.js] [test_storage.js]
[test_timezone.js] [test_timezone.js]
[test_timezone_changes.js]
[test_timezone_definition.js] [test_timezone_definition.js]
[test_unifinder_utils.js] [test_unifinder_utils.js]
[test_utils.js] [test_utils.js]

Просмотреть файл

@ -15,15 +15,22 @@ Otherwise manual corrections will get dropped when pushing the update.
""" """
from __future__ import absolute_import
import argparse, ftplib, json, os, os.path, re, shutil, subprocess, sys, tarfile, tempfile import argparse, ftplib, json, os, os.path, re, shutil, subprocess, sys, tarfile, tempfile
from collections import OrderedDict from collections import OrderedDict
from datetime import date, timedelta
# Keep timezone changes from this date onwards. If the zones.json file is becoming
# too large, consider changing to a later date.
HISTORY_CUTOFF = 20180101
FUTURE_CUTOFF = 20221231
class TimezoneUpdater(object): class TimezoneUpdater(object):
""" Timezone updater class, use the run method to do everything automatically""" """ Timezone updater class, use the run method to do everything automatically"""
def __init__(self, tzdata_path, zoneinfo_path, zoneinfo_pure_path): def __init__(self, tzdata_path, zoneinfo_pure_path):
self.tzdata_path = tzdata_path self.tzdata_path = tzdata_path
self.zoneinfo_path = zoneinfo_path
self.zoneinfo_pure_path = zoneinfo_pure_path self.zoneinfo_pure_path = zoneinfo_pure_path
def download_tzdata(self): def download_tzdata(self):
@ -55,14 +62,7 @@ class TimezoneUpdater(object):
def run_vzic(self, vzic_path): def run_vzic(self, vzic_path):
"""Use vzic to create ICS versions of the data.""" """Use vzic to create ICS versions of the data."""
# Use `vzic` to create 'pure' and 'non-pure' zone files. # Use `vzic` to create zone files.
sys.stderr.write("Exporting zone info to %s\n" % self.zoneinfo_path)
subprocess.check_call([
vzic_path,
"--olson-dir", self.tzdata_path,
"--output-dir", self.zoneinfo_path
], stdout=sys.stderr)
sys.stderr.write("Exporting pure zone info to %s\n" % self.zoneinfo_pure_path) sys.stderr.write("Exporting pure zone info to %s\n" % self.zoneinfo_pure_path)
subprocess.check_call([ subprocess.check_call([
vzic_path, vzic_path,
@ -85,7 +85,7 @@ class TimezoneUpdater(object):
def read_zones_tab(self): def read_zones_tab(self):
"""Read zones.tab for latitude and longitude data.""" """Read zones.tab for latitude and longitude data."""
lat_long_data = {} lat_long_data = {}
with open(os.path.join(self.zoneinfo_path, "zones.tab"), "r") as tab: with open(os.path.join(self.zoneinfo_pure_path, "zones.tab"), "r") as tab:
for line in tab: for line in tab:
if len(line) < 19: if len(line) < 19:
sys.stderr.write("Line in zones.tab not long enough: %s\n" % line.strip()) sys.stderr.write("Line in zones.tab not long enough: %s\n" % line.strip())
@ -97,40 +97,137 @@ class TimezoneUpdater(object):
return lat_long_data return lat_long_data
def read_ics(self, filename, lat_long_data): def read_ics(self, filename, lat_long_data):
"""Read a single zone's ICS files. """Read a single zone's ICS files."""
We keep only the lines we want, and we use the pure version of RRULE if
the versions differ. See Asia/Jerusalem for an example."""
with open(os.path.join(self.zoneinfo_path, filename), "r") as zone:
zoneinfo = zone.readlines()
with open(os.path.join(self.zoneinfo_pure_path, filename), "r") as zone: with open(os.path.join(self.zoneinfo_pure_path, filename), "r") as zone:
zoneinfo_pure = zone.readlines() zoneinfo_pure = zone.readlines()
ics_data = [] # Loop through the lines of the file, splitting it into components.
for i in range(0, len(zoneinfo)): components = []
line = zoneinfo[i] current_component = None
key = line[:line.find(":")] for i in range(0, len(zoneinfo_pure)):
line = zoneinfo_pure[i].rstrip()
[key, value] = line.split(":", 1)
if key == "BEGIN": if line in ["BEGIN:STANDARD", "BEGIN:DAYLIGHT"]:
if line != "BEGIN:VCALENDAR\r\n": current_component = {"line": i, "type": value}
ics_data.append(line)
elif key == "END": elif line in ["END:STANDARD", "END:DAYLIGHT"]:
if line != "END:VCALENDAR\r\n": components.append(current_component)
ics_data.append(line) current_component = None
elif key in ("TZID", "TZOFFSETFROM", "TZOFFSETTO", "TZNAME", "DTSTART"):
ics_data.append(line) elif current_component:
elif key == "RRULE": if key == "RDATE":
if line == zoneinfo_pure[i]: if "rdates" not in current_component:
ics_data.append(line) current_component["rdates"] = []
current_component["rdates"].append(value)
else: else:
sys.stderr.write("Using pure version of %s\n" % filename[:-4]) current_component[key] = value
ics_data.append(zoneinfo_pure[i])
# Create a copy of each component for every date that it started.
# Later, we'll sort them into order of starting date.
components_by_start_date = {}
for component in components:
max_rdate = int(component["DTSTART"][0:8])
components_by_start_date[max_rdate] = component
if "rdates" in component:
for rdate in component["rdates"]:
rdate = int(rdate[0:8])
max_rdate = max(rdate, max_rdate)
components_by_start_date[rdate] = component
component["valid_rdates"] = filter(
lambda rd: FUTURE_CUTOFF >= int(rd[0:8]) >= HISTORY_CUTOFF,
component["rdates"]
)
component["max_date"] = max_rdate
# Sort, and keep only the components in use since the cutoff date.
kept_components = []
finished = False
for key in sorted(components_by_start_date.keys(), reverse=True):
if key > FUTURE_CUTOFF:
continue
component = components_by_start_date[key]
if finished and "RRULE" not in component:
continue
if "used" in component:
continue
component["used"] = True
kept_components.append(component)
if key <= HISTORY_CUTOFF:
finished = True
for i in range(len(kept_components)):
component = kept_components[i]
last = i == len(kept_components) - 1
# In this block of code, we attempt to match what vzic does when
# creating "Outlook-compatible" timezone files. This is to minimise
# changes in our zones.json file. And to be more Outlook-compatible.
if int(component["DTSTART"][0:8]) < HISTORY_CUTOFF:
if not last and "valid_rdates" in component and len(component["valid_rdates"]) > 0:
component["DTSTART"] = component["valid_rdates"][0]
continue
# Change the start date to what it would've been in 1970.
start_date = "19700101"
start_time = "T000000"
if "RRULE" in component:
rrule = dict(part.split("=") for part in component["RRULE"].split(";"))
bymonth = int(rrule["BYMONTH"])
weekday = rrule["BYDAY"].lstrip("-012345")
weekday_index = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"].index(weekday)
if "BYMONTHDAY" in rrule:
bymonthday = list(int(d) for d in rrule["BYMONTHDAY"].split(","))
for day in bymonthday:
test_day = date(1970, bymonth, day)
if test_day.weekday() == weekday_index:
start_date = test_day.strftime("%Y%m%d")
start_time = component["DTSTART"][8:]
break
elif "BYDAY" in rrule:
which_weekday = int(rrule["BYDAY"].rstrip("AEFHMORSTUW"))
days_matching = [0]
test_day = date(1970, bymonth, 1)
while test_day.month == bymonth:
if test_day.weekday() == weekday_index:
days_matching.append(test_day)
test_day = test_day + timedelta(days=1)
start_date = days_matching[which_weekday].strftime("%Y%m%d")
start_time = component["DTSTART"][8:]
component["DTSTART"] = start_date + start_time
# Sort the components back into the order they appeared in the original file.
# This is to minimise changes in our zones.json file.
kept_components.sort(key=lambda b: b["line"])
zone_name = filename[:-4]
ics = []
for component in kept_components:
ics_lines = []
ics_lines.append("BEGIN:%s" % component["type"])
if len(kept_components) == 1 or len(component["TZOFFSETFROM"]) != 5:
ics_lines.append("TZOFFSETFROM:%s" % component["TZOFFSETTO"])
else:
ics_lines.append("TZOFFSETFROM:%s" % component["TZOFFSETFROM"])
ics_lines.append("TZOFFSETTO:%s" % component["TZOFFSETTO"])
if "TZNAME" in component:
ics_lines.append("TZNAME:%s" % component["TZNAME"])
ics_lines.append("DTSTART:%s" % component["DTSTART"])
if "RRULE" in component:
ics_lines.append("RRULE:%s" % component["RRULE"])
elif len(kept_components) > 1 and "valid_rdates" in component:
for rdate in component["valid_rdates"]:
ics_lines.append("RDATE:%s" % rdate)
ics_lines.append("END:%s" % component["type"])
ics.append("\r\n".join(ics_lines))
zone_data = { zone_data = {
"ics": "".join(ics_data).rstrip() "ics": ics,
} }
zone_name = filename[:-4]
if zone_name in lat_long_data: if zone_name in lat_long_data:
zone_data["latitude"] = lat_long_data[zone_name][0] zone_data["latitude"] = lat_long_data[zone_name][0]
zone_data["longitude"] = lat_long_data[zone_name][1] zone_data["longitude"] = lat_long_data[zone_name][1]
@ -144,6 +241,8 @@ class TimezoneUpdater(object):
zones = {} zones = {}
for entry in os.listdir(path): for entry in os.listdir(path):
if entry == "Etc":
continue
fullpath = os.path.join(path, entry) fullpath = os.path.join(path, entry)
if os.path.isdir(fullpath): if os.path.isdir(fullpath):
zones.update(self.read_dir(fullpath, process_zone, os.path.join(prefix, entry))) zones.update(self.read_dir(fullpath, process_zone, os.path.join(prefix, entry)))
@ -219,7 +318,7 @@ class TimezoneUpdater(object):
self.run_vzic(vzic_path) self.run_vzic(vzic_path)
lat_long_data = self.read_zones_tab() lat_long_data = self.read_zones_tab()
newzones = self.read_dir(self.zoneinfo_path, newzones = self.read_dir(self.zoneinfo_pure_path,
lambda fn: self.read_ics(fn, lat_long_data)) lambda fn: self.read_ics(fn, lat_long_data))
newaliases = self.link_removed_zones(zonesjson["zones"], newzones, links) newaliases = self.link_removed_zones(zonesjson["zones"], newzones, links)
@ -307,21 +406,13 @@ def main():
# A test data update must occur before the zones.json file gets updated to have meaningful data # A test data update must occur before the zones.json file gets updated to have meaningful data
create_test_data(json_file) create_test_data(json_file)
zoneinfo_path = tempfile.mkdtemp(prefix="zones")
zoneinfo_pure_path = tempfile.mkdtemp(prefix="zones") zoneinfo_pure_path = tempfile.mkdtemp(prefix="zones")
updater = TimezoneUpdater(args.tzdata_path, zoneinfo_path, zoneinfo_pure_path) updater = TimezoneUpdater(args.tzdata_path, zoneinfo_pure_path)
updater.run(json_file, tzprops_file, args.vzic_path) updater.run(json_file, tzprops_file, args.vzic_path)
# Clean up. # Clean up.
shutil.rmtree(zoneinfo_path)
shutil.rmtree(zoneinfo_pure_path) shutil.rmtree(zoneinfo_pure_path)
print """
When updating timezone definitions, please check the zones America/Campo_Grande,
America/Cuiaba, and America/Sao_Paulo are not reset to their previous buggy state.
See bug 1515937 for more information.
"""
if __name__ == "__main__": if __name__ == "__main__":
main() main()

Просмотреть файл

@ -498,7 +498,12 @@
}, },
"Africa/Casablanca": { "Africa/Casablanca": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20180325T020000\r\nRDATE:20180325T020000\r\nRDATE:20180617T020000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:20180513T030000\r\nRDATE:20180513T030000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20190609T020000\r\nRDATE:20190609T020000\r\nRDATE:20200524T020000\r\nRDATE:20210516T020000\r\nRDATE:20220508T020000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20181028T030000\r\nRDATE:20181028T030000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:20190505T030000\r\nRDATE:20190505T030000\r\nRDATE:20200419T030000\r\nRDATE:20210411T030000\r\nRDATE:20220327T030000\r\nEND:DAYLIGHT"
], ],
"latitude": "+0333900", "latitude": "+0333900",
"longitude": "-0073500" "longitude": "-0073500"
@ -548,7 +553,12 @@
}, },
"Africa/El_Aaiun": { "Africa/El_Aaiun": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:-0100\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20180325T020000\r\nRDATE:20180325T020000\r\nRDATE:20180617T020000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:20180513T030000\r\nRDATE:20180513T030000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20181028T030000\r\nRDATE:20181028T030000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:+00\r\nDTSTART:20190505T030000\r\nRDATE:20190505T030000\r\nRDATE:20200419T030000\r\nRDATE:20210411T030000\r\nRDATE:20220327T030000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:+01\r\nDTSTART:20190609T020000\r\nRDATE:20190609T020000\r\nRDATE:20200524T020000\r\nRDATE:20210516T020000\r\nRDATE:20220508T020000\r\nEND:STANDARD"
], ],
"latitude": "+0270900", "latitude": "+0270900",
"longitude": "-0131200" "longitude": "-0131200"
@ -744,7 +754,8 @@
}, },
"Africa/Sao_Tome": { "Africa/Sao_Tome": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0000\r\nTZNAME:GMT\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:WAT\r\nDTSTART:20180101T010000\r\nRDATE:20180101T010000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:GMT\r\nDTSTART:20190101T020000\r\nRDATE:20190101T020000\r\nEND:STANDARD"
], ],
"latitude": "+0002000", "latitude": "+0002000",
"longitude": "+0064400" "longitude": "+0064400"
@ -988,8 +999,10 @@
}, },
"America/Campo_Grande": { "America/Campo_Grande": {
"ics": [ "ics": [
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19701101T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT", "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20181104T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:19700215T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3SU\r\nEND:STANDARD" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19700101T000000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20180218T000000\r\nRDATE:20180218T000000\r\nRDATE:20190217T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20200216T000000\r\nRDATE:20200216T000000\r\nRDATE:20210221T000000\r\nRDATE:20220220T000000\r\nEND:STANDARD"
], ],
"latitude": "-0202700", "latitude": "-0202700",
"longitude": "-0543700" "longitude": "-0543700"
@ -1054,8 +1067,10 @@
}, },
"America/Cuiaba": { "America/Cuiaba": {
"ics": [ "ics": [
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19701101T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT", "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20181104T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:19700215T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3SU\r\nEND:STANDARD" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19700101T000000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20180218T000000\r\nRDATE:20180218T000000\r\nRDATE:20190217T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20200216T000000\r\nRDATE:20200216T000000\r\nRDATE:20210221T000000\r\nRDATE:20220220T000000\r\nEND:STANDARD"
], ],
"latitude": "-0153500", "latitude": "-0153500",
"longitude": "-0560500" "longitude": "-0560500"
@ -1174,8 +1189,10 @@
}, },
"America/Grand_Turk": { "America/Grand_Turk": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nTZNAME:EST\r\nDTSTART:19701101T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD", "BEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nTZNAME:EST\r\nDTSTART:20181104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nTZNAME:EDT\r\nDTSTART:19700308T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nTZNAME:EDT\r\nDTSTART:20190310T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0400\r\nTZNAME:AST\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0400\r\nTZNAME:EDT\r\nDTSTART:20180311T020000\r\nRDATE:20180311T020000\r\nEND:DAYLIGHT"
], ],
"latitude": "+0212800", "latitude": "+0212800",
"longitude": "-0710800" "longitude": "-0710800"
@ -1454,8 +1471,12 @@
}, },
"America/Metlakatla": { "America/Metlakatla": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0900\r\nTZNAME:AKST\r\nDTSTART:19701101T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD", "BEGIN:STANDARD\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0900\r\nTZNAME:AKST\r\nDTSTART:20191103T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0900\r\nTZOFFSETTO:-0800\r\nTZNAME:AKDT\r\nDTSTART:19700308T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0900\r\nTZOFFSETTO:-0800\r\nTZNAME:AKDT\r\nDTSTART:20200308T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0800\r\nTZNAME:PST\r\nDTSTART:20181104T020000\r\nRDATE:20181104T020000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0900\r\nTZNAME:AKST\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0900\r\nTZOFFSETTO:-0800\r\nTZNAME:AKDT\r\nDTSTART:20180311T020000\r\nRDATE:20180311T020000\r\nEND:DAYLIGHT",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0800\r\nTZNAME:AKDT\r\nDTSTART:20190310T020000\r\nRDATE:20190310T020000\r\nEND:DAYLIGHT"
], ],
"latitude": "+0550737", "latitude": "+0550737",
"longitude": "-1313435" "longitude": "-1313435"
@ -1696,8 +1717,11 @@
}, },
"America/Santiago": { "America/Santiago": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:19700405T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYMONTHDAY=2,3,4,5,6,7,8;BYDAY=SU\r\nEND:STANDARD", "BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20190407T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYMONTHDAY=2,3,4,5,6,7,8;BYDAY=SU\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19700906T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYMONTHDAY=2,3,4,5,6,7,8;BYDAY=SU\r\nEND:DAYLIGHT" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20190908T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYMONTHDAY=2,3,4,5,6,7,8;BYDAY=SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20180812T000000\r\nRDATE:20180812T000000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0400\r\nTZNAME:-04\r\nDTSTART:20180513T000000\r\nRDATE:20180513T000000\r\nEND:STANDARD"
], ],
"latitude": "-0332700", "latitude": "-0332700",
"longitude": "-0704000" "longitude": "-0704000"
@ -1711,8 +1735,10 @@
}, },
"America/Sao_Paulo": { "America/Sao_Paulo": {
"ics": [ "ics": [
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0200\r\nTZNAME:-02\r\nDTSTART:19701101T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT", "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0200\r\nTZNAME:-02\r\nDTSTART:20181104T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0200\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:19700215T000000\r\nRRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3SU\r\nEND:STANDARD" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0200\r\nTZNAME:-02\r\nDTSTART:19700101T000000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0200\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20180218T000000\r\nRDATE:20180218T000000\r\nRDATE:20190217T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0300\r\nTZOFFSETTO:-0300\r\nTZNAME:-03\r\nDTSTART:20200216T000000\r\nRDATE:20200216T000000\r\nRDATE:20210221T000000\r\nRDATE:20220220T000000\r\nEND:STANDARD"
], ],
"latitude": "-0233200", "latitude": "-0233200",
"longitude": "-0463700" "longitude": "-0463700"
@ -1871,7 +1897,8 @@
}, },
"Antarctica/Casey": { "Antarctica/Casey": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0800\r\nTZOFFSETTO:+0800\r\nTZNAME:+08\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0800\r\nTZOFFSETTO:+1100\r\nTZNAME:+11\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1100\r\nTZOFFSETTO:+0800\r\nTZNAME:+08\r\nDTSTART:20180311T040000\r\nRDATE:20180311T040000\r\nEND:STANDARD"
], ],
"latitude": "-0661700", "latitude": "-0661700",
"longitude": "+1103100" "longitude": "+1103100"
@ -2130,7 +2157,7 @@
"Asia/Famagusta": { "Asia/Famagusta": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0200\r\nTZNAME:EET\r\nDTSTART:19701025T040000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD", "BEGIN:STANDARD\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0200\r\nTZNAME:EET\r\nDTSTART:19701025T040000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0300\r\nTZNAME:EEST\r\nDTSTART:19700329T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0300\r\nTZNAME:EEST\r\nDTSTART:20180325T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT"
], ],
"latitude": "+0350700", "latitude": "+0350700",
"longitude": "+0335700" "longitude": "+0335700"
@ -2365,7 +2392,8 @@
}, },
"Asia/Pyongyang": { "Asia/Pyongyang": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0900\r\nTZOFFSETTO:+0900\r\nTZNAME:KST\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0900\r\nTZOFFSETTO:+0830\r\nTZNAME:KST\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0830\r\nTZOFFSETTO:+0900\r\nTZNAME:KST\r\nDTSTART:20180504T233000\r\nRDATE:20180504T233000\r\nEND:STANDARD"
], ],
"latitude": "+0390100", "latitude": "+0390100",
"longitude": "+1254500" "longitude": "+1254500"
@ -2386,7 +2414,8 @@
}, },
"Asia/Qyzylorda": { "Asia/Qyzylorda": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0500\r\nTZOFFSETTO:+0500\r\nTZNAME:+05\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0600\r\nTZOFFSETTO:+0600\r\nTZNAME:+06\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0600\r\nTZOFFSETTO:+0500\r\nTZNAME:+05\r\nDTSTART:20181221T000000\r\nRDATE:20181221T000000\r\nEND:STANDARD"
], ],
"latitude": "+0444800", "latitude": "+0444800",
"longitude": "+0652800" "longitude": "+0652800"
@ -2463,7 +2492,9 @@
}, },
"Asia/Tehran": { "Asia/Tehran": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0330\r\nTZOFFSETTO:+0330\r\nTZNAME:+0330\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0400\r\nTZOFFSETTO:+0330\r\nTZNAME:+0330\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0330\r\nTZOFFSETTO:+0430\r\nTZNAME:+0430\r\nDTSTART:20180321T235959\r\nRDATE:20180321T235959\r\nRDATE:20190321T235959\r\nRDATE:20200320T235959\r\nRDATE:20210321T235959\r\nRDATE:20220321T235959\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0430\r\nTZOFFSETTO:+0330\r\nTZNAME:+0330\r\nDTSTART:20180921T235959\r\nRDATE:20180921T235959\r\nRDATE:20190921T235959\r\nRDATE:20200920T235959\r\nRDATE:20210921T235959\r\nRDATE:20220921T235959\r\nEND:STANDARD"
], ],
"latitude": "+0354000", "latitude": "+0354000",
"longitude": "+0512600" "longitude": "+0512600"
@ -3158,7 +3189,8 @@
}, },
"Europe/Volgograd": { "Europe/Volgograd": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0400\r\nTZOFFSETTO:+0400\r\nTZNAME:+04\r\nDTSTART:19700101T000000\r\nEND:STANDARD" "BEGIN:STANDARD\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0400\r\nTZNAME:+04\r\nDTSTART:20181028T020000\r\nRDATE:20181028T020000\r\nEND:STANDARD",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0400\r\nTZOFFSETTO:+0300\r\nTZNAME:+03\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
], ],
"latitude": "+0484400", "latitude": "+0484400",
"longitude": "+0442500" "longitude": "+0442500"
@ -3312,8 +3344,11 @@
}, },
"Pacific/Easter": { "Pacific/Easter": {
"ics": [ "ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0600\r\nTZNAME:-06\r\nDTSTART:19700404T220000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SA\r\nEND:STANDARD", "BEGIN:STANDARD\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0600\r\nTZNAME:-06\r\nDTSTART:20190406T220000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SA\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0500\r\nTZNAME:-05\r\nDTSTART:19700905T220000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1SA\r\nEND:DAYLIGHT" "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0500\r\nTZNAME:-05\r\nDTSTART:20190907T220000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1SA\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0600\r\nTZNAME:-06\r\nDTSTART:19700101T000000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0500\r\nTZNAME:-05\r\nDTSTART:20180811T220000\r\nRDATE:20180811T220000\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0600\r\nTZNAME:-06\r\nDTSTART:20180512T220000\r\nRDATE:20180512T220000\r\nEND:STANDARD"
], ],
"latitude": "-0270900", "latitude": "-0270900",
"longitude": "-1092600" "longitude": "-1092600"