diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java
new file mode 100644
index 000000000..9315e1c54
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl;
+
+/**
+ * Base47 encoding is best fit for encoding varible length numbers in JavaScript strings.
+ *
+ * 47 = (int)(93 / 2), where 94 is the number of ASCII characters representable in JavaScript string
+ * without escaping. These characters are encoded by one byte in UTF-8 charset. All other character require
+ * either escaping or two or more bytes in UTF-8.
+ *
+ * We divide 93 by 2 for the following trick. Representing integers takes 5 bytes in Base93. However,
+ * we often need smaller integers that might be represented by one or two bytes. By each Base93 digit we
+ * can encode both part of the number and a flag indicating whether the number contains one more digit.
+ *
+ * @author Alexey Andreev
+ */
+public class Base46 {
+ public static void encodeUnsigned(StringBuilder sb, int number) {
+ boolean hasMore;
+ do {
+ int digit = number % 46;
+ number /= 46;
+ hasMore = number > 0;
+ digit = digit * 2 + (hasMore ? 1 : 0);
+ sb.append(encodeDigit(digit));
+ } while (hasMore);
+ }
+
+ public static void encode(StringBuilder sb, int number) {
+ encodeUnsigned(sb, Math.abs(number) * 2 + (number >= 0 ? 0 : 1));
+ }
+
+ public static void encodeUnsigned(StringBuilder sb, long number) {
+ boolean hasMore;
+ do {
+ int digit = (int)(number % 46);
+ number /= 46;
+ hasMore = number > 0;
+ digit = digit * 2 + (hasMore ? 1 : 0);
+ sb.append(encodeDigit(digit));
+ } while (hasMore);
+ }
+
+ public static void encode(StringBuilder sb, long number) {
+ encodeUnsigned(sb, Math.abs(number) * 2 + (number >= 0 ? 0 : 1));
+ }
+
+ public static int decodeUnsigned(CharFlow seq) {
+ int number = 0;
+ int pos = 1;
+ boolean hasMore;
+ do {
+ int digit = decodeDigit(seq.characters[seq.pointer++]);
+ hasMore = digit % 2 == 1;
+ number += pos * (digit / 2);
+ pos *= 46;
+ } while (hasMore);
+ return number;
+ }
+
+ public static int decode(CharFlow seq) {
+ int number = decodeUnsigned(seq);
+ int result = number / 2;
+ if (number % 2 != 0) {
+ result = -result;
+ }
+ return result;
+ }
+
+ public static long decodeUnsignedLong(CharFlow seq) {
+ long number = 0;
+ long pos = 1;
+ boolean hasMore;
+ do {
+ int digit = decodeDigit(seq.characters[seq.pointer++]);
+ hasMore = digit % 2 == 1;
+ number += pos * (digit / 2);
+ pos *= 46;
+ } while (hasMore);
+ return number;
+ }
+
+ public static long decodeLong(CharFlow seq) {
+ long number = decodeUnsigned(seq);
+ long result = number / 2;
+ if (number % 2 != 0) {
+ result = -result;
+ }
+ return result;
+ }
+
+ public static char encodeDigit(int digit) {
+ if (digit < 2) {
+ return (char)(digit + ' ');
+ } else if (digit < 59) {
+ return (char)(digit + 1 + ' ');
+ } else {
+ return (char)(digit + 2 + ' ');
+ }
+ }
+
+ public static int decodeDigit(char c) {
+ if (c < '"') {
+ return c - ' ';
+ } else if (c < '\\') {
+ return c - ' ' - 1;
+ } else {
+ return c - ' ' - 2;
+ }
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java
new file mode 100644
index 000000000..3a7944d8e
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class CharFlow {
+ public final char[] characters;
+ public int pointer;
+
+ public CharFlow(char[] characters) {
+ this.characters = characters;
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java
new file mode 100644
index 000000000..87abbed84
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl.tz;
+
+import org.joda.time.DateTimeZone;
+import org.teavm.classlib.impl.Base46;
+import org.teavm.model.MethodReference;
+import org.teavm.platform.metadata.MetadataGenerator;
+import org.teavm.platform.metadata.MetadataGeneratorContext;
+import org.teavm.platform.metadata.ResourceMap;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class TimeZoneGenerator implements MetadataGenerator {
+ private static int[] divisors = { 60_000, 300_000, 1800_000, 3600_000 };
+ public static int RESOLUTION_MINUTE = 0;
+ public static int RESOLUTION_5_MINUTES = 1;
+ public static int RESOLUTION_HALF_HOUR = 2;
+ public static int RESOLUTION_HOUR = 3;
+
+
+ @Override
+ public ResourceMap> generateMetadata(
+ MetadataGeneratorContext context, MethodReference method) {
+ ResourceMap> result = context.createResourceMap();
+ for (String id : DateTimeZone.getAvailableIDs()) {
+ int sepIndex = id.indexOf('/');
+ String areaName = id.substring(0, sepIndex);
+ String locationName = id.substring(sepIndex + 1);
+ ResourceMap area = result.get(areaName);
+ if (area == null) {
+ area = context.createResourceMap();
+ result.put(areaName, area);
+ }
+
+ DateTimeZone tz = DateTimeZone.forID(id);
+ TimeZoneResource tzRes = context.createResource(TimeZoneResource.class);
+ tzRes.setAbbreviation(locationName);
+ tzRes.setData(encodeData(tz));
+ area.put(locationName, tzRes);
+ }
+
+ return result;
+ }
+
+ public String encodeData(DateTimeZone tz) {
+ // Find resolution
+ int resolution = RESOLUTION_HOUR;
+ long current = 0;
+ long offset = tz.getOffset(0);
+ while (true) {
+ long next = tz.nextTransition(current);
+ if (next == current) {
+ break;
+ }
+ current = next;
+
+ int nextOffset = tz.getOffset(next);
+ if (nextOffset == offset) {
+ continue;
+ }
+
+ offset = nextOffset;
+ resolution = getResolution(resolution, current);
+ resolution = getResolution(resolution, offset);
+ if (resolution == 0) {
+ break;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ Base46.encode(sb, resolution);
+
+ current = 0;
+ offset = tz.getOffset(0);
+ int divisor = divisors[resolution];
+ long last = 0;
+ long lastOffset = offset / divisor;
+ Base46.encode(sb, lastOffset);
+ while (true) {
+ long next = tz.nextTransition(current);
+ if (next == current) {
+ break;
+ }
+ current = next;
+
+ int nextOffset = tz.getOffset(next);
+ if (nextOffset == offset) {
+ continue;
+ }
+
+ offset = nextOffset;
+ long newTime = current / divisor;
+ long newOffset = offset / divisor;
+ Base46.encodeUnsigned(sb, newTime - last);
+ Base46.encode(sb, newOffset - lastOffset);
+ last = newTime;
+ lastOffset = newOffset;
+ }
+
+ return sb.toString();
+ }
+
+ private int getResolution(int currentResolution, long value) {
+ while (currentResolution > 0 && value % divisors[currentResolution] != 0) {
+ --currentResolution;
+ }
+ return currentResolution;
+ }
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java
new file mode 100644
index 000000000..baa06ca4c
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl.tz;
+
+import org.teavm.platform.metadata.Resource;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public interface TimeZoneResource extends Resource {
+ String getAbbreviation();
+
+ void setAbbreviation(String abbreviation);
+
+ String getData();
+
+ void setData(String data);
+}
diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java
new file mode 100644
index 000000000..079892016
--- /dev/null
+++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl.tz;
+
+import org.teavm.platform.metadata.MetadataProvider;
+import org.teavm.platform.metadata.ResourceMap;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class TimeZoneResourceProvider {
+ public static TimeZoneResource getTimeZone(String id) {
+ int sepIndex = id.indexOf('/');
+ String areaName = id.substring(0, sepIndex);
+ String locationName = id.substring(sepIndex + 1);
+ ResourceMap area = getResource().get(areaName);
+ if (area == null) {
+ return null;
+ }
+ return area.get(locationName);
+ }
+
+ @MetadataProvider(TimeZoneGenerator.class)
+ private static native ResourceMap> getResource();
+}
diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java b/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java
new file mode 100644
index 000000000..ff04563a5
--- /dev/null
+++ b/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.teavm.classlib.impl;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class Base46Test {
+ @Test
+ public void encode() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = -65536; i <= 65536; ++i) {
+ sb.setLength(0);
+ Base46.encode(sb, i);
+ System.out.println(i + " - " + sb);
+ CharFlow flow = new CharFlow(sb.toString().toCharArray());
+ int num = Base46.decode(flow);
+ assertEquals(i, num);
+ }
+ }
+}
diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java
new file mode 100644
index 000000000..58b1857a1
--- /dev/null
+++ b/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java
@@ -0,0 +1,16 @@
+package org.teavm.classlib.java.util;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.teavm.classlib.impl.tz.TimeZoneResourceProvider;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class TimeZoneTest {
+ @Test
+ public void resourceProvided() {
+ assertNotNull(TimeZoneResourceProvider.getTimeZone("Europe/Moscow"));
+ }
+}