Start to implement time zones

This commit is contained in:
Alexey Andreev 2015-05-12 21:40:57 +03:00
parent 7277696870
commit b264e34ef8
8 changed files with 409 additions and 0 deletions

View File

@ -69,6 +69,11 @@
<artifactId>jzlib</artifactId> <artifactId>jzlib</artifactId>
<version>1.1.3</version> <version>1.1.3</version>
</dependency> </dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.7</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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;
/**
* <p>Base47 encoding is best fit for encoding varible length numbers in JavaScript strings.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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;
}
}
}

View File

@ -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;
}
}

View File

@ -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<ResourceMap<TimeZoneResource>> generateMetadata(
MetadataGeneratorContext context, MethodReference method) {
ResourceMap<ResourceMap<TimeZoneResource>> 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<TimeZoneResource> 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;
}
}

View File

@ -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);
}

View File

@ -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<TimeZoneResource> area = getResource().get(areaName);
if (area == null) {
return null;
}
return area.get(locationName);
}
@MetadataProvider(TimeZoneGenerator.class)
private static native ResourceMap<ResourceMap<TimeZoneResource>> getResource();
}

View File

@ -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);
}
}
}

View File

@ -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"));
}
}