/* * jBrowserDriver (TM) * Copyright (C) 2014-2016 Machine Publishers, LLC and the jBrowserDriver contributors * https://github.com/MachinePublishers/jBrowserDriver * * 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 com.machinepublishers.jbrowserdriver; import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TimeZone; /** * Browser timezone and daylight savings settings. * Currently one limitation is that locale for formatting of date strings will always be en-US. */ public class Timezone { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-HH-mm"); static { dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } private static final Map<Integer, String> offsets = new HashMap<Integer, String>(); static { offsets.put(-36000000, "HAST"); offsets.put(-32400000, "AKST"); offsets.put(-28800000, "PST"); offsets.put(-25200000, "MST"); offsets.put(-21600000, "CST"); offsets.put(-18000000, "EST"); offsets.put(0, "UTC"); offsets.put(3600000, "CET"); offsets.put(7200000, "EET"); offsets.put(10800000, "EAT"); offsets.put(19800000, "IST"); offsets.put(21600000, "BST"); offsets.put(28800000, "CST"); offsets.put(32400000, "JST"); offsets.put(34200000, "ACST"); offsets.put(39600000, "SST"); offsets.put(43200000, "NZST"); offsets.put(46800000, "MIT"); } private static final Map<Integer, String> daylightTimzones = new HashMap<Integer, String>(); static { daylightTimzones.put(-36000000, "HADT"); daylightTimzones.put(-32400000, "AKDT"); daylightTimzones.put(-28800000, "PDT"); daylightTimzones.put(-25200000, "MDT"); daylightTimzones.put(-21600000, "CDT"); daylightTimzones.put(-18000000, "EDT"); daylightTimzones.put(0, "UTC"); daylightTimzones.put(3600000, "CEST"); daylightTimzones.put(7200000, "EEST"); daylightTimzones.put(10800000, "EAT"); daylightTimzones.put(19800000, "IST"); daylightTimzones.put(21600000, "BST"); daylightTimzones.put(28800000, "CST"); daylightTimzones.put(32400000, "JST"); daylightTimzones.put(34200000, "ACDT"); daylightTimzones.put(39600000, "SST"); daylightTimzones.put(43200000, "NZDT"); daylightTimzones.put(46800000, "MIT"); } public static final Timezone UTC = new Timezone("UTC"); public static final Timezone AFRICA_ABIDJAN = new Timezone("Africa/Abidjan"); public static final Timezone AFRICA_ACCRA = new Timezone("Africa/Accra"); public static final Timezone AFRICA_ADDISABABA = new Timezone("Africa/Addis_Ababa"); public static final Timezone AFRICA_ALGIERS = new Timezone("Africa/Algiers"); public static final Timezone AFRICA_CAIRO = new Timezone("Africa/Cairo"); public static final Timezone AFRICA_CASABLANCA = new Timezone("Africa/Casablanca"); public static final Timezone AFRICA_DARESSALAAM = new Timezone("Africa/Dar_es_Salaam"); public static final Timezone AFRICA_FREETOWN = new Timezone("Africa/Freetown"); public static final Timezone AFRICA_JOHANNESBURG = new Timezone("Africa/Johannesburg"); public static final Timezone AFRICA_KHARTOUM = new Timezone("Africa/Khartoum"); public static final Timezone AFRICA_KINSHASA = new Timezone("Africa/Kinshasa"); public static final Timezone AFRICA_LAGOS = new Timezone("Africa/Lagos"); public static final Timezone AFRICA_MOGADISHU = new Timezone("Africa/Mogadishu"); public static final Timezone AFRICA_NAIROBI = new Timezone("Africa/Nairobi"); public static final Timezone AFRICA_TRIPOLI = new Timezone("Africa/Tripoli"); public static final Timezone AMERICA_ANCHORAGE = new Timezone("America/Anchorage"); public static final Timezone AMERICA_BELIZE = new Timezone("America/Belize"); public static final Timezone AMERICA_BOGOTA = new Timezone("America/Bogota"); public static final Timezone AMERICA_CANCUN = new Timezone("America/Cancun"); public static final Timezone AMERICA_CAYMAN = new Timezone("America/Cayman"); public static final Timezone AMERICA_CHICAGO = new Timezone("America/Chicago"); public static final Timezone AMERICA_COSTARICA = new Timezone("America/Costa_Rica"); public static final Timezone AMERICA_DENVER = new Timezone("America/Denver"); public static final Timezone AMERICA_GUATEMALA = new Timezone("America/Guatemala"); public static final Timezone AMERICA_JAMAICA = new Timezone("America/Jamaica"); public static final Timezone AMERICA_LIMA = new Timezone("America/Lima"); public static final Timezone AMERICA_LOSANGELES = new Timezone("America/Los_Angeles"); public static final Timezone AMERICA_MEXICOCITY = new Timezone("America/Mexico_City"); public static final Timezone AMERICA_MONTERREY = new Timezone("America/Monterrey"); public static final Timezone AMERICA_MONTREAL = new Timezone("America/Montreal"); public static final Timezone AMERICA_NEWYORK = new Timezone("America/New_York"); public static final Timezone AMERICA_PANAMA = new Timezone("America/Panama"); public static final Timezone AMERICA_PHOENIX = new Timezone("America/Phoenix"); public static final Timezone AMERICA_TIJUANA = new Timezone("America/Tijuana"); public static final Timezone AMERICA_TORONTO = new Timezone("America/Toronto"); public static final Timezone AMERICA_VANCOUVER = new Timezone("America/Vancouver"); public static final Timezone AMERICA_WINNIPEG = new Timezone("America/Winnipeg"); public static final Timezone ASIA_BEIRUT = new Timezone("Asia/Beirut"); public static final Timezone ASIA_CALCUTTA = new Timezone("Asia/Calcutta"); public static final Timezone ASIA_DAMASCUS = new Timezone("Asia/Damascus"); public static final Timezone ASIA_DHAKA = new Timezone("Asia/Dhaka"); public static final Timezone ASIA_ISTANBUL = new Timezone("Asia/Istanbul"); public static final Timezone ASIA_NOVOSIBIRSK = new Timezone("Asia/Novosibirsk"); public static final Timezone ASIA_QATAR = new Timezone("Asia/Qatar"); public static final Timezone ASIA_SEOUL = new Timezone("Asia/Seoul"); public static final Timezone ASIA_SHANGHAI = new Timezone("Asia/Shanghai"); public static final Timezone ASIA_SINGAPORE = new Timezone("Asia/Singapore"); public static final Timezone ASIA_TELAVIV = new Timezone("Asia/Tel_Aviv"); public static final Timezone ASIA_TOKYO = new Timezone("Asia/Tokyo"); public static final Timezone EUROPE_AMSTERDAM = new Timezone("Europe/Amsterdam"); public static final Timezone EUROPE_ATHENS = new Timezone("Europe/Athens"); public static final Timezone EUROPE_BERLIN = new Timezone("Europe/Berlin"); public static final Timezone EUROPE_BRUSSELS = new Timezone("Europe/Brussels"); public static final Timezone EUROPE_BUCHAREST = new Timezone("Europe/Bucharest"); public static final Timezone EUROPE_BUDAPEST = new Timezone("Europe/Budapest"); public static final Timezone EUROPE_COPENHAGEN = new Timezone("Europe/Copenhagen"); public static final Timezone EUROPE_ISTANBUL = new Timezone("Europe/Istanbul"); public static final Timezone EUROPE_KIEV = new Timezone("Europe/Kiev"); public static final Timezone EUROPE_LONDON = new Timezone("Europe/London"); public static final Timezone EUROPE_MADRID = new Timezone("Europe/Madrid"); public static final Timezone EUROPE_MINSK = new Timezone("Europe/Minsk"); public static final Timezone EUROPE_MOSCOW = new Timezone("Europe/Moscow"); public static final Timezone EUROPE_PARIS = new Timezone("Europe/Paris"); public static final Timezone EUROPE_PRAGUE = new Timezone("Europe/Prague"); public static final Timezone EUROPE_ROME = new Timezone("Europe/Rome"); public static final Timezone EUROPE_SOFIA = new Timezone("Europe/Sofia"); public static final Timezone EUROPE_STOCKHOLM = new Timezone("Europe/Stockholm"); public static final Timezone EUROPE_VIENNA = new Timezone("Europe/Vienna"); public static final Timezone EUROPE_WARSAW = new Timezone("Europe/Warsaw"); public static final Timezone EUROPE_ZURICH = new Timezone("Europe/Zurich"); public static final Timezone PACIFIC_AUCKLAND = new Timezone("Pacific/Auckland"); public static final Timezone PACIFIC_FIJI = new Timezone("Pacific/Fiji"); public static final Timezone PACIFIC_HONOLULU = new Timezone("Pacific/Honolulu"); private static final Map<String, Timezone> zonesByName; public static final Set<Timezone> ALL_ZONES; static { Map<String, Timezone> zonesByNameTmp = new HashMap<String, Timezone>(); Field[] fields = Timezone.class.getDeclaredFields(); for (Field field : fields) { try { Object obj = field.get(null); if (obj instanceof Timezone) { Timezone cur = (Timezone) field.get(null); zonesByNameTmp.put(cur.timeZoneName, cur); } } catch (Throwable t) {} } ALL_ZONES = Collections.unmodifiableSet(new HashSet<Timezone>(zonesByNameTmp.values())); zonesByName = Collections.unmodifiableMap(zonesByNameTmp); } private String script; private final String timeZoneName; private Timezone(String timeZoneName) { this.timeZoneName = timeZoneName; } /** * Get a Timezone according to Java's standard locale names. * The names are based on {@link TimeZone} IDs. E.g., <code>America/New_York</code> * * @param locale * TimeZone ID * @return Timezone */ public static Timezone byName(String locale) { return zonesByName.get(locale); } /** * @return The locale name for this Timezone */ public String name() { return timeZoneName; } private static String timeZoneDesc(boolean daylight, int rawOffset, int timeZoneMinutes, int daylightMinutes) { int totalOffsetMinutes = timeZoneMinutes - (daylight ? daylightMinutes : 0); int formattedOffsetHours = Math.abs(totalOffsetMinutes / 60); int formattedOffsetMinutes = Math.abs(totalOffsetMinutes) - (formattedOffsetHours * 60); String timeZoneDesc = (totalOffsetMinutes <= 0 ? "+" : "-") + (formattedOffsetHours < 10 ? "0" + formattedOffsetHours : "" + formattedOffsetHours) + (formattedOffsetMinutes == 0 ? "00" : (formattedOffsetMinutes < 10 ? "0" + formattedOffsetMinutes : formattedOffsetMinutes)); return daylight ? new StringBuilder().append(timeZoneDesc).append(" (").append(daylightTimzones.get(rawOffset)).append(")").toString() : new StringBuilder().append(timeZoneDesc).append(" (").append(offsets.get(rawOffset)).append(")").toString(); } private void init() { TimeZone timeZone = TimeZone.getTimeZone(timeZoneName); int[][] daylightSavings = daylightSavings(timeZone); int[] daylightSavingsStart = daylightSavings == null ? null : daylightSavings[0]; int[] daylightSavingsEnd = daylightSavings == null ? null : daylightSavings[1]; int timeZoneMinutes = -1 * timeZone.getRawOffset() / 1000 / 60; int daylightMinutes = timeZone.getDSTSavings() / 1000 / 60; String timeZoneDesc = timeZoneDesc(false, timeZone.getRawOffset(), timeZoneMinutes, daylightMinutes); String timeZoneDescDaylight = timeZoneDesc(true, timeZone.getRawOffset(), timeZoneMinutes, daylightMinutes); StringBuilder builder = new StringBuilder(); if (daylightSavingsStart == null || daylightSavingsEnd == null) { builder.append("var isDaylightSavings = false;"); } else { builder.append("var start = tmpDate.getUTCMonth() > ").append(daylightSavingsStart[0]).append("? 8"); builder.append(": (tmpDate.getUTCMonth() < ").append(daylightSavingsStart[0]).append("? -8 : 0);"); builder.append("start += tmpDate.getUTCDate() > ").append(daylightSavingsStart[1]).append("? 4"); builder.append(": (tmpDate.getUTCDate() < ").append(daylightSavingsStart[1]).append("? -4 : 0);"); builder.append("start += tmpDate.getUTCHours() > ").append(daylightSavingsStart[2]).append("? 2"); builder.append(": (tmpDate.getUTCHours() < ").append(daylightSavingsStart[2]).append("? -2 : 0);"); builder.append("start += tmpDate.getUTCMinutes() > ").append(daylightSavingsStart[3]).append("? 1"); builder.append(": (tmpDate.getUTCMinutes() < ").append(daylightSavingsStart[3]).append("? -1 : 0);"); builder.append("var end = tmpDate.getUTCMonth() < ").append(daylightSavingsEnd[0]).append("? 8"); builder.append(": (tmpDate.getUTCMonth() > ").append(daylightSavingsEnd[0]).append("? -8 : 0);"); builder.append("end += tmpDate.getUTCDate() < ").append(daylightSavingsEnd[1]).append("? 4"); builder.append(": (tmpDate.getUTCDate() > ").append(daylightSavingsEnd[1]).append("? -4 : 0);"); builder.append("end += tmpDate.getUTCHours() < ").append(daylightSavingsEnd[2]).append("? 2"); builder.append(": (tmpDate.getUTCHours() > ").append(daylightSavingsEnd[2]).append("? -2 : 0);"); builder.append("end += tmpDate.getUTCMinutes() < ").append(daylightSavingsEnd[3]).append("? 1"); builder.append(": (tmpDate.getUTCMinutes() > ").append(daylightSavingsEnd[3]).append("? -1 : 0);"); builder.append("var isDaylightSavings = start > 0 && end > 0;"); } String isDaylightSavings = builder.toString(); builder = new StringBuilder(); builder.append("var timeZoneDesc = '").append(timeZoneDesc).append("';"); builder.append("if(isDaylightSavings){"); builder.append("timeZoneDesc = '").append(timeZoneDescDaylight).append("';"); builder.append("}"); String timeZoneDescExpr = builder.toString(); builder = new StringBuilder(); builder.append("var tmpDate = new Date(this.getTime() + ").append(timeZone.getRawOffset()).append(");"); builder.append(isDaylightSavings); builder.append("if(isDaylightSavings){"); builder.append(" tmpDate = new Date(tmpDate.getTime() + ").append(timeZone.getDSTSavings()).append(");"); builder.append("}"); String tmpDate = builder.toString(); builder = new StringBuilder(); builder.append("var weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];"); builder.append("var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', ") .append("'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];"); String weekdayAndMonthArrays = builder.toString(); builder = new StringBuilder(); builder.append("var minutes = tmpDate.getUTCMinutes();"); builder.append("minutes = minutes < 10? '0'+minutes : minutes;"); builder.append("var seconds = tmpDate.getUTCSeconds();"); builder.append("seconds = seconds < 10? '0'+seconds : seconds;"); builder.append("var hours = tmpDate.getUTCHours();"); builder.append("var amPM = hours < 12? 'AM' : 'PM';"); builder.append("hours = hours % 12;"); builder.append("hours = hours == 0? 12 : hours;"); String time12Hour = builder.toString(); builder = new StringBuilder(); builder.append("var minutes = tmpDate.getUTCMinutes();"); builder.append("minutes = minutes < 10? '0'+minutes : minutes;"); builder.append("var seconds = tmpDate.getUTCSeconds();"); builder.append("seconds = seconds < 10? '0'+seconds : seconds;"); builder.append("var hours = tmpDate.getUTCHours();"); builder.append("hours = hours < 10? '0' + hours : hours;"); String time24Hour = builder.toString(); builder = new StringBuilder(); builder.append("Date.prototype.getTimezoneOffset = function(){"); builder.append(tmpDate); builder.append("if(isDaylightSavings){"); builder.append(" return ").append(timeZoneMinutes).append(" - ").append(daylightMinutes).append(";"); builder.append("}"); builder.append("return ").append(timeZoneMinutes).append(";"); builder.append("};"); builder.append("Date.prototype.getFullYear = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCFullYear();"); builder.append("};"); builder.append("Date.prototype.getYear = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCFullYear() % 100;"); builder.append("};"); builder.append("Date.prototype.getMonth = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCMonth();"); builder.append("};"); builder.append("Date.prototype.getDate = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCDate();"); builder.append("};"); builder.append("Date.prototype.getDay = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCDay();"); builder.append("};"); builder.append("Date.prototype.getHours = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCHours();"); builder.append("};"); builder.append("Date.prototype.getMinutes = function(){"); builder.append(tmpDate); builder.append("return tmpDate.getUTCMinutes();"); builder.append("};"); builder.append("Date.prototype.toDateString = function(){"); builder.append(weekdayAndMonthArrays); builder.append(tmpDate); builder.append("return weekday[tmpDate.getUTCDay()] + ' ' + month[tmpDate.getUTCMonth()] ") .append("+ ' ' + tmpDate.getUTCDate() + ' ' + tmpDate.getUTCFullYear();"); builder.append("};"); //TODO update this when JS engine supports optional args: dateObj.toLocaleDateString([locales [, options]]) builder.append("Date.prototype.toLocaleDateString = function(){"); builder.append(tmpDate); builder.append("return (tmpDate.getUTCMonth() + 1) + '/' + tmpDate.getUTCDate() + '/' + tmpDate.getUTCFullYear();"); builder.append("};"); //TODO update this when JS engine supports optional args: dateObj.toLocaleString([locales[, options]]) builder.append("Date.prototype.toLocaleString = function(){"); builder.append(tmpDate); builder.append(time12Hour); builder.append("return (tmpDate.getUTCMonth() + 1) + '/' + tmpDate.getUTCDate() + '/' + tmpDate.getUTCFullYear() ") .append("+ ', ' + hours + ':' + minutes + ':' + seconds + ' ' + amPM;"); builder.append("};"); //TODO update this when JS engine supports optional args: dateObj.toLocaleTimeString([locales[, options]]) builder.append("Date.prototype.toLocaleTimeString = function(){"); builder.append(tmpDate); builder.append(time12Hour); builder.append("return hours + ':' + minutes + ':' + seconds + ' ' + amPM;"); builder.append("};"); builder.append("Date.prototype.toString = function(){"); builder.append(weekdayAndMonthArrays); builder.append(tmpDate); builder.append(time24Hour); builder.append(timeZoneDescExpr); builder.append("return weekday[tmpDate.getUTCDay()] + ' ' + month[tmpDate.getUTCMonth()] + ' ' + tmpDate.getUTCDate() ") .append("+ ' ' + tmpDate.getUTCFullYear() + ' ' + hours + ':' + minutes + ':' + seconds + ' GMT'+timeZoneDesc;"); builder.append("};"); builder.append("Date.prototype.toTimeString = function(){"); builder.append(tmpDate); builder.append(time24Hour); builder.append(timeZoneDescExpr); builder.append("return hours + ':' + minutes + ':' + seconds + ' GMT'+timeZoneDesc;"); builder.append("};"); this.script = builder.toString(); } String script() { if (script == null) { init(); } return script; } private static int[][] daylightSavings(TimeZone timeZone) { final int curYear = Calendar.getInstance().get(Calendar.YEAR); final Calendar calendar = Calendar.getInstance(timeZone); calendar.setLenient(false); calendar.setTime(new Date(0)); Date prevDate = null; boolean foundStart = false; boolean foundEnd = false; final int[][] span = new int[2][4]; final int[] pos = new int[] { 0, 30 }; for (int month = 0; month < 12; month++) { for (int day = 1; day < 32; day++) { for (int hour = 0; hour < 24; hour++) { for (int minutePos = 0; minutePos < pos.length; minutePos++) { calendar.set(curYear, month, day, hour, pos[minutePos], 0); try { calendar.getTime().getTime(); } catch (Throwable t) { continue; } if (prevDate == null) { prevDate = calendar.getTime(); } else { if (!foundStart && timeZone.inDaylightTime(calendar.getTime()) && !timeZone.inDaylightTime(prevDate)) { span[0] = toInts(dateFormat.format(calendar.getTime()).split("-")); --span[0][0]; foundStart = true; } if (!foundEnd && !timeZone.inDaylightTime(calendar.getTime()) && timeZone.inDaylightTime(prevDate)) { span[1] = toInts(dateFormat.format(prevDate).split("-")); --span[1][0]; foundEnd = true; } if (foundStart && foundEnd) { return span; } prevDate = calendar.getTime(); } } } } } return null; } private static int[] toInts(String[] strings) { int[] ints = new int[strings.length]; for (int i = 0; i < ints.length; i++) { ints[i] = Integer.parseInt(strings[i]); } return ints; } }