Last active
October 1, 2020 04:39
-
-
Save dmikurube/23899471a8d9042d2649c92ebe7d1d10 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: 'java' | |
repositories { | |
mavenCentral() | |
} | |
dependencies { | |
compile 'joda-time:joda-time:2.9.2' | |
} | |
sourceSets { | |
main { | |
java { | |
srcDir '.' | |
} | |
} | |
} | |
test { | |
testLogging { | |
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError' | |
} | |
} | |
task run(type:JavaExec) { | |
main = project.hasProperty('main') ? project.getProperty('main') : 'JodaShortNames' | |
classpath = sourceSets.main.runtimeClasspath | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.DateTimeException; | |
import java.time.Instant; | |
import java.time.OffsetDateTime; | |
import java.time.ZoneId; | |
import java.time.ZoneOffset; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Date; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.Set; | |
import java.util.TimeZone; | |
import java.util.TreeSet; | |
import org.joda.time.DateTimeZone; | |
import org.joda.time.format.DateTimeFormat; | |
public class JodaShortNames { | |
public static void main(final String[] args) { | |
final Instant summer = OffsetDateTime.of(2020, 6, 23, 12, 0, 0, 0, ZoneOffset.UTC).toInstant(); | |
final Instant winter = OffsetDateTime.of(2020, 12, 23, 12, 0, 0, 0, ZoneOffset.UTC).toInstant(); | |
final Set<String> unorderedIds = DateTimeZone.getAvailableIDs(); | |
final List<String> ids = new ArrayList<>(unorderedIds); | |
Collections.sort(ids); | |
final TreeSet<String> shortNames = new TreeSet<>(); | |
for (final String id : ids) { | |
final DateTimeZone jodaZone = DateTimeZone.forID(id); | |
final String jodaSummer = jodaZone.getShortName(summer.toEpochMilli()); | |
shortNames.add(jodaSummer); | |
final String jodaWinter = jodaZone.getShortName(winter.toEpochMilli()); | |
shortNames.add(jodaWinter); | |
final ZoneId javaZone; | |
try { | |
javaZone = ZoneId.of(id); | |
} catch (final DateTimeException ex) { | |
System.out.printf("[S] %s: %s (N/A)\n", id, jodaSummer); | |
System.out.printf("[W] %s: %s (N/A)\n", id, jodaWinter); | |
continue; | |
} | |
final TimeZone javaTimeZone = TimeZone.getTimeZone(javaZone); | |
final String javaSummer = javaTimeZone.getDisplayName( | |
javaTimeZone.inDaylightTime(Date.from(summer)), TimeZone.SHORT, Locale.ROOT); | |
System.out.printf("[S] %s: %s %s %s\n", | |
id, jodaSummer, (jodaSummer.equals(javaSummer) ? "==" : "!="), javaSummer); | |
final String javaWinter = javaTimeZone.getDisplayName( | |
javaTimeZone.inDaylightTime(Date.from(winter)), TimeZone.SHORT, Locale.ROOT); | |
System.out.printf("[W] %s: %s %s %s\n", | |
id, jodaWinter, (jodaWinter.equals(javaWinter) ? "==" : "!="), javaWinter); | |
} | |
System.out.println("\nFrom short names:\n"); | |
for (final String shortName : shortNames) { | |
System.out.print(shortName); | |
try { | |
System.out.print(": " + DateTimeZone.forID(shortName).toString()); | |
} catch (final IllegalArgumentException ex) { | |
System.out.print(": (error)"); | |
} | |
try { | |
System.out.print(": " + parseDateTimeZone(shortName).toString()); | |
} catch (final RuntimeException ex) { | |
System.out.print(": (error)"); | |
} | |
try { | |
System.out.println(": " + parseJodaDateTimeZone(shortName).toString()); | |
} catch (final RuntimeException ex) { | |
System.out.println(": (error)"); | |
} | |
} | |
} | |
private static DateTimeZone parseDateTimeZone(final String s) { | |
if(s.startsWith("+") || s.startsWith("-")) { | |
return DateTimeZone.forID(s); | |
} else if (s.equals("Z")) { | |
return DateTimeZone.UTC; | |
} else { | |
try { | |
int rawOffset = (int) DateTimeFormat.forPattern("z").parseMillis(s); | |
if(rawOffset == 0) { | |
return DateTimeZone.UTC; | |
} | |
int offset = rawOffset / -1000; | |
int h = offset / 3600; | |
int m = offset % 3600; | |
return DateTimeZone.forOffsetHoursMinutes(h, m); | |
} catch (IllegalArgumentException ex) { | |
// parseMillis failed | |
} | |
// TimeZone.getTimeZone returns GMT zone if given timezone id is not found | |
// we want to only return timezone if exact match, otherwise exception | |
if (availableTimeZoneNames.contains(s)) { | |
//return TimeZone.getTimeZone(s); | |
return DateTimeZone.forID(s); | |
} | |
return null; | |
} | |
} | |
private static Set<String> availableTimeZoneNames = new HashSet<>(DateTimeZone.getAvailableIDs()); | |
public static org.joda.time.DateTimeZone parseJodaDateTimeZone(final String timeZoneName) { | |
org.joda.time.DateTimeZone jodaDateTimeZoneTemporary = null; | |
try { | |
// Use TimeZone#forID, not TimeZone#getTimeZone. | |
// Because getTimeZone returns GMT even if given timezone id is not found. | |
jodaDateTimeZoneTemporary = org.joda.time.DateTimeZone.forID(timeZoneName); | |
} catch (IllegalArgumentException ex) { | |
jodaDateTimeZoneTemporary = null; | |
} | |
final org.joda.time.DateTimeZone jodaDateTimeZone = jodaDateTimeZoneTemporary; | |
// Embulk has accepted to parse Joda-Time's time zone IDs in Timestamps since v0.2.0 | |
// although the formats are based on Ruby's strptime. Joda-Time's time zone IDs are | |
// continuously to be accepted with higher priority than Ruby's time zone IDs. | |
if (jodaDateTimeZone != null && (timeZoneName.startsWith("+") || timeZoneName.startsWith("-"))) { | |
return jodaDateTimeZone; | |
} else if (timeZoneName.equals("Z")) { | |
return org.joda.time.DateTimeZone.UTC; | |
} else { | |
try { | |
// DateTimeFormat.forPattern("z").parseMillis(s) is incorrect, but kept for compatibility as of now. | |
// | |
// The offset of PDT (Pacific Daylight Time) should be -07:00. | |
// DateTimeFormat.forPattern("z").parseMillis("PDT") however returns 8 hours (-08:00). | |
// DateTimeFormat.forPattern("z").parseMillis("PDT") == 28800000 | |
// https://github.com/JodaOrg/joda-time/blob/v2.9.2/src/main/java/org/joda/time/DateTimeUtils.java#L446 | |
// | |
// Embulk has used it to parse time zones for a very long time since it was v0.1. | |
// https://github.com/embulk/embulk/commit/b97954a5c78397e1269bbb6979d6225dfceb4e05 | |
// | |
// It is kept as -08:00 for compatibility as of now. | |
// | |
// TODO: Make time zone parsing consistent. | |
// @see <a href="https://github.com/embulk/embulk/issues/860">https://github.com/embulk/embulk/issues/860</a> | |
int rawOffset = (int) org.joda.time.format.DateTimeFormat.forPattern("z").parseMillis(timeZoneName); | |
if (rawOffset == 0) { | |
return org.joda.time.DateTimeZone.UTC; | |
} | |
int offset = rawOffset / -1000; | |
int h = offset / 3600; | |
int m = offset % 3600; | |
return org.joda.time.DateTimeZone.forOffsetHoursMinutes(h, m); | |
} catch (IllegalArgumentException ex) { | |
// parseMillis failed | |
} | |
if (jodaDateTimeZone != null && JODA_TIME_ZONES.contains(timeZoneName)) { | |
return jodaDateTimeZone; | |
} | |
// Parsing Ruby-style time zones in lower priority than Joda-Time because | |
// TimestampParser has parsed time zones with Joda-Time for a long time | |
// since ancient. The behavior is kept for compatibility. | |
// | |
// The following time zone IDs are duplicated in Ruby and Joda-Time 2.9.2 | |
// while Ruby does not care summer time and Joda-Time cares summer time. | |
// "CET", "EET", "Egypt", "Iran", "MET", "WET" | |
// | |
// Some zone IDs (ex. "PDT") are parsed by DateTimeFormat#parseMillis as shown above. | |
final int rubyStyleTimeOffsetInSecond = RubyTimeZoneTab.dateZoneToDiff(timeZoneName); | |
if (rubyStyleTimeOffsetInSecond != Integer.MIN_VALUE) { | |
return org.joda.time.DateTimeZone.forOffsetMillis(rubyStyleTimeOffsetInSecond * 1000); | |
} | |
return null; | |
} | |
} | |
private static final Set<String> JODA_TIME_ZONES = | |
Collections.unmodifiableSet(org.joda.time.DateTimeZone.getAvailableIDs()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RubyTimeZoneTab { | |
// Ported zones_source in ext/date/date_parse.c | |
private static int getOffsetFromZonesSource(String z) { | |
switch (z) { | |
case "ut": | |
return 0 * 3600; | |
case "gmt": | |
return 0 * 3600; | |
case "est": | |
return -5 * 3600; | |
case "edt": | |
return -4 * 3600; | |
case "cst": | |
return -6 * 3600; | |
case "cdt": | |
return -5 * 3600; | |
case "mst": | |
return -7 * 3600; | |
case "mdt": | |
return -6 * 3600; | |
case "pst": | |
return -8 * 3600; | |
case "pdt": | |
return -7 * 3600; | |
case "a": | |
return 1 * 3600; | |
case "b": | |
return 2 * 3600; | |
case "c": | |
return 3 * 3600; | |
case "d": | |
return 4 * 3600; | |
case "e": | |
return 5 * 3600; | |
case "f": | |
return 6 * 3600; | |
case "g": | |
return 7 * 3600; | |
case "h": | |
return 8 * 3600; | |
case "i": | |
return 9 * 3600; | |
case "k": | |
return 10 * 3600; | |
case "l": | |
return 11 * 3600; | |
case "m": | |
return 12 * 3600; | |
case "n": | |
return -1 * 3600; | |
case "o": | |
return -2 * 3600; | |
case "p": | |
return -3 * 3600; | |
case "q": | |
return -4 * 3600; | |
case "r": | |
return -5 * 3600; | |
case "s": | |
return -6 * 3600; | |
case "t": | |
return -7 * 3600; | |
case "u": | |
return -8 * 3600; | |
case "v": | |
return -9 * 3600; | |
case "w": | |
return -10 * 3600; | |
case "x": | |
return -11 * 3600; | |
case "y": | |
return -12 * 3600; | |
case "z": | |
return 0 * 3600; | |
case "utc": | |
return 0 * 3600; | |
case "wet": | |
return 0 * 3600; | |
case "at": | |
return -2 * 3600; | |
case "brst": | |
return -2 * 3600; | |
case "ndt": | |
return -(2 * 3600 + 1800); | |
case "art": | |
return -3 * 3600; | |
case "adt": | |
return -3 * 3600; | |
case "brt": | |
return -3 * 3600; | |
case "clst": | |
return -3 * 3600; | |
case "nst": | |
return -(3 * 3600 + 1800); | |
case "ast": | |
return -4 * 3600; | |
case "clt": | |
return -4 * 3600; | |
case "akdt": | |
return -8 * 3600; | |
case "ydt": | |
return -8 * 3600; | |
case "akst": | |
return -9 * 3600; | |
case "hadt": | |
return -9 * 3600; | |
case "hdt": | |
return -9 * 3600; | |
case "yst": | |
return -9 * 3600; | |
case "ahst": | |
return -10 * 3600; | |
case "cat": | |
return -10 * 3600; | |
case "hast": | |
return -10 * 3600; | |
case "hst": | |
return -10 * 3600; | |
case "nt": | |
return -11 * 3600; | |
case "idlw": | |
return -12 * 3600; | |
case "bst": | |
return 1 * 3600; | |
case "cet": | |
return 1 * 3600; | |
case "fwt": | |
return 1 * 3600; | |
case "met": | |
return 1 * 3600; | |
case "mewt": | |
return 1 * 3600; | |
case "mez": | |
return 1 * 3600; | |
case "swt": | |
return 1 * 3600; | |
case "wat": | |
return 1 * 3600; | |
case "west": | |
return 1 * 3600; | |
case "cest": | |
return 2 * 3600; | |
case "eet": | |
return 2 * 3600; | |
case "fst": | |
return 2 * 3600; | |
case "mest": | |
return 2 * 3600; | |
case "mesz": | |
return 2 * 3600; | |
case "sast": | |
return 2 * 3600; | |
case "sst": | |
return 2 * 3600; | |
case "bt": | |
return 3 * 3600; | |
case "eat": | |
return 3 * 3600; | |
case "eest": | |
return 3 * 3600; | |
case "msk": | |
return 3 * 3600; | |
case "msd": | |
return 4 * 3600; | |
case "zp4": | |
return 4 * 3600; | |
case "zp5": | |
return 5 * 3600; | |
case "ist": | |
return 5 * 3600 + 1800; | |
case "zp6": | |
return 6 * 3600; | |
case "wast": | |
return 7 * 3600; | |
case "cct": | |
return 8 * 3600; | |
case "sgt": | |
return 8 * 3600; | |
case "wadt": | |
return 8 * 3600; | |
case "jst": | |
return 9 * 3600; | |
case "kst": | |
return 9 * 3600; | |
case "east": | |
return 10 * 3600; | |
case "gst": | |
return 10 * 3600; | |
case "eadt": | |
return 11 * 3600; | |
case "idle": | |
return 12 * 3600; | |
case "nzst": | |
return 12 * 3600; | |
case "nzt": | |
return 12 * 3600; | |
case "nzdt": | |
return 13 * 3600; | |
case "afghanistan": | |
return 16200; | |
case "alaskan": | |
return -32400; | |
case "arab": | |
return 10800; | |
case "arabian": | |
return 14400; | |
case "arabic": | |
return 10800; | |
case "atlantic": | |
return -14400; | |
case "aus central": | |
return 34200; | |
case "aus eastern": | |
return 36000; | |
case "azores": | |
return -3600; | |
case "canada central": | |
return -21600; | |
case "cape verde": | |
return -3600; | |
case "caucasus": | |
return 14400; | |
case "cen. australia": | |
return 34200; | |
case "central america": | |
return -21600; | |
case "central asia": | |
return 21600; | |
case "central europe": | |
return 3600; | |
case "central european": | |
return 3600; | |
case "central pacific": | |
return 39600; | |
case "central": | |
return -21600; | |
case "china": | |
return 28800; | |
case "dateline": | |
return -43200; | |
case "e. africa": | |
return 10800; | |
case "e. australia": | |
return 36000; | |
case "e. europe": | |
return 7200; | |
case "e. south america": | |
return -10800; | |
case "eastern": | |
return -18000; | |
case "egypt": | |
return 7200; | |
case "ekaterinburg": | |
return 18000; | |
case "fiji": | |
return 43200; | |
case "fle": | |
return 7200; | |
case "greenland": | |
return -10800; | |
case "greenwich": | |
return 0; | |
case "gtb": | |
return 7200; | |
case "hawaiian": | |
return -36000; | |
case "india": | |
return 19800; | |
case "iran": | |
return 12600; | |
case "jerusalem": | |
return 7200; | |
case "korea": | |
return 32400; | |
case "mexico": | |
return -21600; | |
case "mid-atlantic": | |
return -7200; | |
case "mountain": | |
return -25200; | |
case "myanmar": | |
return 23400; | |
case "n. central asia": | |
return 21600; | |
case "nepal": | |
return 20700; | |
case "new zealand": | |
return 43200; | |
case "newfoundland": | |
return -12600; | |
case "north asia east": | |
return 28800; | |
case "north asia": | |
return 25200; | |
case "pacific sa": | |
return -14400; | |
case "pacific": | |
return -28800; | |
case "romance": | |
return 3600; | |
case "russian": | |
return 10800; | |
case "sa eastern": | |
return -10800; | |
case "sa pacific": | |
return -18000; | |
case "sa western": | |
return -14400; | |
case "samoa": | |
return -39600; | |
case "se asia": | |
return 25200; | |
case "malay peninsula": | |
return 28800; | |
case "south africa": | |
return 7200; | |
case "sri lanka": | |
return 21600; | |
case "taipei": | |
return 28800; | |
case "tasmania": | |
return 36000; | |
case "tokyo": | |
return 32400; | |
case "tonga": | |
return 46800; | |
case "us eastern": | |
return -18000; | |
case "us mountain": | |
return -25200; | |
case "vladivostok": | |
return 36000; | |
case "w. australia": | |
return 28800; | |
case "w. central africa": | |
return 3600; | |
case "w. europe": | |
return 3600; | |
case "west asia": | |
return 18000; | |
case "west pacific": | |
return 36000; | |
case "yakutsk": | |
return 32400; | |
default: | |
return Integer.MIN_VALUE; | |
} | |
} | |
// Ported date_zone_to_diff in ext/date/date_parse.c | |
public static int dateZoneToDiff(String zone) { | |
String z = zone.toLowerCase(); | |
final boolean dst; | |
if (z.endsWith(" daylight time")) { | |
z = z.substring(0, z.length() - " daylight time".length()); | |
dst = true; | |
} else if (z.endsWith(" standard time")) { | |
z = z.substring(0, z.length() - " standard time".length()); | |
dst = false; | |
} else if (z.endsWith(" dst")) { | |
z = z.substring(0, z.length() - " dst".length()); | |
dst = true; | |
} else { | |
dst = false; | |
} | |
int offsetFromZonesSource; | |
if ((offsetFromZonesSource = getOffsetFromZonesSource(z)) != Integer.MIN_VALUE) { | |
if (dst) { | |
offsetFromZonesSource += 3600; | |
} | |
return offsetFromZonesSource; | |
} | |
if (z.startsWith("gmt") || z.startsWith("utc")) { | |
z = z.substring(3, z.length()); // remove "gmt" or "utc" | |
} | |
final boolean sign; | |
if (z.charAt(0) == '+') { | |
sign = true; | |
} else if (z.charAt(0) == '-') { | |
sign = false; | |
} else { | |
// if z doesn't start with "+" or "-", invalid | |
return Integer.MIN_VALUE; | |
} | |
z = z.substring(1); | |
int hour = 0; | |
int min = 0; | |
int sec = 0; | |
if (z.contains(":")) { | |
final String[] splited = z.split(":"); | |
if (splited.length == 2) { | |
hour = Integer.parseInt(splited[0]); | |
min = Integer.parseInt(splited[1]); | |
} else { | |
hour = Integer.parseInt(splited[0]); | |
min = Integer.parseInt(splited[1]); | |
sec = Integer.parseInt(splited[2]); | |
} | |
} else if (z.contains(",") || z.contains(".")) { | |
// TODO min = Rational(fr.to_i, 10**fr.size) * 60 | |
String[] splited = z.split("[\\.,]"); | |
hour = Integer.parseInt(splited[0]); | |
min = (int) (Integer.parseInt(splited[1]) * 60 / Math.pow(10, splited[1].length())); | |
} else { | |
final int len = z.length(); | |
if (len % 2 != 0) { | |
if (len >= 1) { | |
hour = Integer.parseInt(z.substring(0, 1)); | |
} | |
if (len >= 3) { | |
min = Integer.parseInt(z.substring(1, 3)); | |
} | |
if (len >= 5) { | |
sec = Integer.parseInt(z.substring(3, 5)); | |
} | |
} else { | |
if (len >= 2) { | |
hour = Integer.parseInt(z.substring(0, 2)); | |
} | |
if (len >= 4) { | |
min = Integer.parseInt(z.substring(2, 4)); | |
} | |
if (len >= 6) { | |
sec = Integer.parseInt(z.substring(4, 6)); | |
} | |
} | |
} | |
final int offset = hour * 3600 + min * 60 + sec; | |
return sign ? offset : -offset; | |
} | |
private RubyTimeZoneTab() {} | |
} |
Author
dmikurube
commented
May 13, 2020
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment