My previous question was closed as duplicate of Which three-letter time zone IDs are not deprecated? but I believe this is something completely different.
The other question is about the java.util.TimeZone class.
My question is about the java.text.SimpleDateFormat class.
As I already mentioned in the original question, a simple test revealed that these 2 classes do not support the same timezone abbreviations.
For example:
TimeZone supports "CTT" but SimpleDateFormat does not
SimpleDateFormat supports "CEST" but TimeZone does not
Where (or how) can I find a full list of abbreviations that SimpleDateFormat is able to parse when using "z" in the parse string, and what each abbreviation is considered to mean?
Original question for reference:
[Context: I'm not a developer but need to document existing code]
I understand that there are no standards for timezone abbreviations and so CST can mean Central Standard Time, China Standard Time, Cuba Standard Time, ...
But if I have code like this:
String time = "12:00:00.000 CST Tue Dec 17 2019";
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS zzz EEE MMM dd yyyy");
Date utcTime = sdf.parse(time);
System.out.println(utcTime.toGMTString());
Then the result is:
17 Dec 2019 18:00:00 GMT
So this means that SimpleDateFormat.parse() interprets CST as Central Standard Time / UTC-6.
How/where can I get a full list of timezone abbreviations that SimpleDateFormat.parse() supports AND their meaning (i.e. just knowing that CST is supported is not enough, I need to know that this is interpreted as Central Standard Time, not Cuba Standard Time).
I would expect to find the answer on the Javadocs page for SimpleDateFormat but it only gives PST as example, not a full list.
The Javadocs page for TimeZone says :
For compatibility with JDK 1.1.x, some other three-letter time zone
IDs (such as "PST", "CTT", "AST") are also supported.
But doesn't say which ones.
It also only mentions three-letter IDs, while the code above will also work fine with CEST for example (Central European Summer Time).
If I check the list returned by TimeZone.getAvailableIDs(); it does not contain CEST either, but it does contain items like "Cuba" and "Eire", which do not work in the above code snippet (even when I change zzz to zzzz in the parse string).
So I conclude that java.text.SimpleDateFormat does not support the same abbreviations as java.util.TimeZone.
Where can I find a list of abbreviations that SimpleDateFormat is able to parse?
It sounds like a simple question, but the answer is complicated. The time zone abbreviations supported by Java (whether we are talking the modern DateTimeFormatter or the troublesome and outdated SimpleDateFormat) are not defined by Java itself but by the locale data that Java uses. Complications include
Java can be configured to take locale data from different sources. This means that setting the system property java.locale.providers when you run your program will change the set of abbreviations supported.
The default locale providers were changed from Java 8 to Java 9. So running your program on a different Java version will give you a different set of supported time zone abbreviations.
CLDR, the locale data that are the default from Java 9, come in versions. So every new Java version may come with a different set of supported abbreviations.
Time zone abbreviations come in languages. So a formatter with French locale will support other abbreviations than a formatter with German locale, for example.
Time zone abbreviations are ambiguous. So even if PST is a supported abbreviation, you don’t know whether it parses into Pitcairn Standard Time, Pacific Standard Time or Philippine Standard Time.
One way to get an idea: Run a program formatting dates at different times of year into a time zone abbreviation. Since many time zones use summer time (DST) and use a different abbreviation during summer time, you will want to try to hit a date in the summer and a date in the standard time of year for those zones.
System.out.println("java.locale.providers: " + System.getProperty("java.locale.providers"));
DateTimeFormatter zoneAbbreviationFormatter = DateTimeFormatter.ofPattern("zzz", Locale.FRENCH);
Instant northernSummer = Instant.parse("2019-07-01T00:00:00Z");
Instant southernSummer = Instant.parse("2020-01-01T00:00:00Z");
Set<String> supportedAbbreviations = new TreeSet<>();
for (String zid : ZoneId.getAvailableZoneIds()) {
ZoneId zone = ZoneId.of(zid);
supportedAbbreviations.add(northernSummer.atZone(zone).format(zoneAbbreviationFormatter));
supportedAbbreviations.add(southernSummer.atZone(zone).format(zoneAbbreviationFormatter));
}
System.out.println("" + supportedAbbreviations.size() + " abbreviations");
System.out.println(supportedAbbreviations);
Output on my Java 9 was (scroll right to see it all):
java.locale.providers: null
205 abbreviations
[ACDT, ACST, ACT, ACWST, ADT, AEDT, AEST, AFT, AKDT, AKST, ALMT, AMST, AMT, AQTT, ART, AST, AWST, AZOST, AZOT, AZT, America/Punta_Arenas, Asia/Atyrau, Asia/Barnaul, Asia/Famagusta, Asia/Tomsk, BDT, BNT, BOT, BRST, BRT, BST, BTT, CAT, CCT, CDT, CEST, CET, CHADT, CHAST, CHOT, CHUT, CKT, CLST, CLT, COT, CST, CVT, CXT, ChST, DAVT, DDUT, EASST, EAST, EAT, ECT, EDT, EEST, EET, EGST, EGT, EST, Etc/GMT+1, Etc/GMT+10, Etc/GMT+11, Etc/GMT+12, Etc/GMT+2, Etc/GMT+3, Etc/GMT+4, Etc/GMT+5, Etc/GMT+6, Etc/GMT+7, Etc/GMT+8, Etc/GMT+9, Etc/GMT-1, Etc/GMT-10, Etc/GMT-11, Etc/GMT-12, Etc/GMT-13, Etc/GMT-14, Etc/GMT-2, Etc/GMT-3, Etc/GMT-4, Etc/GMT-5, Etc/GMT-6, Etc/GMT-7, Etc/GMT-8, Etc/GMT-9, Europe/Astrakhan, Europe/Kirov, Europe/Saratov, Europe/Ulyanovsk, FJST, FJT, FKT, FNT, GALT, GAMT, GET, GFT, GILT, GMT, GST, GYT, HADT, HAST, HDT, HKT, HOVT, HST, ICT, IDT, IOT, IRDT, IRKT, IRST, IST, JST, KGT, KOST, KRAT, KST, LHDT, LHST, LINT, MAGT, MART, MAWT, MDT, MEST, MET, MHT, MIST, MMT, MSK, MST, MUT, MVT, MYT, NCT, NDT, NFT, NOVT, NPT, NRT, NST, NUT, NZDT, NZST, OMST, PDT, PET, PETT, PGT, PHOT, PHT, PKT, PMDT, PMST, PONT, PST, PWT, PYST, PYT, RET, ROTT, SAKT, SAMT, SAST, SBT, SCT, SGT, SRET, SRT, SST, SYOT, TAHT, TFT, TJT, TKT, TLT, TMT, TOT, TVT, ULAT, UTC, UYT, UZT, VET, VLAT, VOST, VUT, WAKT, WEST, WET, WFT, WGST, WGT, WIB, WIT, WITA, WSDT, WSST, XJT, YAKT, YEKT]
Edit:
You may modify the code snippet to produce the time zones that each abbreviation may be parsed to too.
I would certainly expect that the formatter can parse the same time zone abbreviations that it can produce by formatting.
Supplement: Code for getting each abbreviation with the time zones that it may parse into:
Set<String> zids = ZoneId.getAvailableZoneIds();
Map <String, List<String>> supportedAbbreviations = new TreeMap<>();
supportedAbbreviations.putAll(zids.stream()
.collect(Collectors.groupingBy(zid -> northernSummer.atZone(ZoneId.of(zid))
.format(zoneAbbreviationFormatter))));
supportedAbbreviations.putAll(zids.stream()
.collect(Collectors.groupingBy(zid -> southernSummer.atZone(ZoneId.of(zid))
.format(zoneAbbreviationFormatter))));
System.out.println("" + supportedAbbreviations.size() + " abbreviations");
supportedAbbreviations.forEach((a, zs) -> System.out.format("%-5s %s%n", a, zs));
Excerpt from the output (still on my Java 9):
205 abbreviations
ACDT [Australia/Yancowinna, Australia/Adelaide, Australia/Broken_Hill, Australia/South]
ACST [Australia/North, Australia/Darwin]
ACT [America/Eirunepe, America/Porto_Acre, Brazil/Acre, America/Rio_Branco]
ACWST [Australia/Eucla]
ADT [Canada/Atlantic, America/Grand_Turk, America/Moncton, Atlantic/Bermuda, America/Halifax, America/Glace_Bay, America/Thule, America/Goose_Bay, SystemV/AST4ADT]
AEDT [Australia/Hobart, Australia/Tasmania, Australia/ACT, Australia/Victoria, Australia/Canberra, Australia/Currie, Australia/NSW, Australia/Sydney, Australia/Melbourne]
AEST [Australia/Queensland, Australia/Brisbane, Australia/Lindeman]
…
WSDT [Pacific/Apia]
WSST [Pacific/Apia]
XJT [Asia/Kashgar, Asia/Urumqi]
YAKT [Asia/Chita, Asia/Yakutsk, Asia/Khandyga]
YEKT [Asia/Yekaterinburg]
By reading the source code of SimpleDateFormat class, the main method for parsing the zone is the method subParseZoneString, as below (source code below comes from jdk1.8.0_151, omitted some part)
/**
* find time zone 'text' matched zoneStrings and set to internal
* calendar.
*/
private int subParseZoneString(String text, int start, CalendarBuilder calb) {
boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
TimeZone currentTimeZone = getTimeZone();
// At this point, check for named time zones by looking through
// the locale data from the TimeZoneNames strings.
// Want to be able to parse both short and long forms.
int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());
TimeZone tz = null;
String[][] zoneStrings = formatData.getZoneStringsWrapper();
String[] zoneNames = null;
int nameIndex = 0;
if (zoneIndex != -1) {
zoneNames = zoneStrings[zoneIndex];
if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
if (nameIndex <= 2) {
// Check if the standard name (abbr) and the daylight name are the same.
useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
}
tz = TimeZone.getTimeZone(zoneNames[0]);
}
}
...
// If fail, try zoneIndex by TimeZone.getDefault().getId(), omitted
...
// If fail, try all elements in zoneStrings, omitted
...
// Handle Daylight Saving, omitted
...
}
From the code, we see that zoneStrings will be the main source for matching against the input text. Each element zoneString in zoneStrings is a string array representing one zone information .For the source, the matching priority is:
The zoneString of the SimpleDateFormat time zone.
The zoneString of the default time zone (TimeZone.getDefault()).
The zoneString in the array order.
By tracing the method call, in DateFormatSymbols class,
/**
* Wrapper method to the getZoneStrings(), which is called from inside
* the java.text package and not to mutate the returned arrays, so that
* it does not need to create a defensive copy.
*/
final String[][] getZoneStringsWrapper() {
if (isSubclassObject()) {
return getZoneStrings();
} else {
return getZoneStringsImpl(false);
}
}
private String[][] getZoneStringsImpl(boolean needsCopy) {
if (zoneStrings == null) {
zoneStrings = TimeZoneNameUtility.getZoneStrings(locale);
}
...
// do array copying, omitted
...
}
The zoneStrings comes from TimeZoneNameUtility class method, which is internal APIs. After some debugging, it is found that the element of zoneStrings is a String array with length 7.
Index 0: Zone Id(TimeZone.getID())
Index 1: Zone Long Name
Index 2: Zone Abbreviation
Index 3: Zone Long Name(For Daylight Saving)
Index 4: Zone Abbreviation(For Daylight Saving)
Index 5,6: Don't know
From the matchZoneString method of SimpleDateFormat class,
private int matchZoneString(String text, int start, String[] zoneNames) {
for (int i = 1; i <= 4; ++i) {
// Checking long and short zones [1 & 2],
// and long and short daylight [3 & 4].
String zoneName = zoneNames[i];
if (text.regionMatches(true, start,
zoneName, 0, zoneName.length())) {
return i;
}
}
return -1;
}
The text input will only match element of zoneStrings from index 1 to 4. Zone ID is not involved in this case.
TL;DR
So now we can explain why
TimeZone supports "CTT" but SimpleDateFormat does not
SimpleDateFormat supports "CEST" but TimeZone does not
"CTT" is a Zone ID but not an abbreviation.
"CEST" is an abbreviation but not a Zone ID.
How/where can I get a full list of timezone abbreviations that
SimpleDateFormat.parse() supports AND their meaning (i.e. just knowing
that CST is supported is not enough, I need to know that this is
interpreted as Central Standard Time, not Cuba Standard Time).
We can try to retrieve all zone abbreviations that can be parsed by SimpleDateFormat and their corresponding Zone ID and Long Name, using the below program by following the matching priority of SimpleDateFormat.
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
public class GetParsableZone {
public static void main(String[] args) throws ParseException {
Locale locale = Locale.getDefault(Locale.Category.FORMAT);
System.out.println("Default Local for getZoneStrings " + locale.toString());
DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale);
String[][] zoneStrings = dateFormatSymbols.getZoneStrings();
Set<String> possibleZoneAbbrs = new HashSet<String>();
for (String[] zoneString : zoneStrings) {
possibleZoneAbbrs.add(zoneString[2]);
possibleZoneAbbrs.add(zoneString[4]);
}
System.out.println("Try to parse all possibleZoneAbbrs");
SimpleDateFormat dateFormatWithZone = new SimpleDateFormat("dd/MM/yyyy zzz");
String template = "12/12/2012 %s";
for (String possibleZoneAbbr : possibleZoneAbbrs) {
dateFormatWithZone.parse(String.format(template, possibleZoneAbbr));
}
System.out.println("All possibleZoneAbbrs can be parsed!");
List<String> orderedPossibleZoneAbbrs = new ArrayList<String>(possibleZoneAbbrs);
Collections.sort(orderedPossibleZoneAbbrs);
String simpleDateFormatZoneId = new SimpleDateFormat().getTimeZone().getID();
String defaultZoneId = TimeZone.getDefault().getID();
Integer simpleDateFormatZoneIndex = getZoneIndex(zoneStrings, simpleDateFormatZoneId);
Integer defaultZoneIndex = getZoneIndex(zoneStrings, defaultZoneId);
System.out.println("Default SimpleDateFormat Time Zone " + new SimpleDateFormat().getTimeZone().getID());
System.out.println("Default Time Zone " + TimeZone.getDefault().getID());
System.out.println("Abbreviation\tZoneId\tLong Name");
for (String orderedPossibleZoneAbbr : orderedPossibleZoneAbbrs) {
// Do matching as SimpleDateFormat
int matchIndex = getZoneIndexMatchAbbreviation(zoneStrings, simpleDateFormatZoneIndex, defaultZoneIndex,
orderedPossibleZoneAbbr);
printIdAndFullName(zoneStrings[matchIndex], orderedPossibleZoneAbbr);
}
}
public static void printIdAndFullName(String[] zoneString, String abbreviation) {
String longName = "";
String id = zoneString[0];
if (zoneString[2].equals(abbreviation)) {
longName = zoneString[1];
} else {
longName = zoneString[3];
}
System.out.println(String.format("%s\t%s\t%s", abbreviation, id, longName));
}
public static final int getZoneIndex(String[][] zoneStrings, String ID) {
for (int index = 0; index < zoneStrings.length; index++) {
if (ID.equals(zoneStrings[index][0])) {
return index;
}
}
return -1;
}
public static boolean isAbbreviationMatchZoneString(String[] zoneString, String abbreviation) {
return zoneString[2].equals(abbreviation) || zoneString[4].equals(abbreviation);
}
public static int getZoneIndexMatchAbbreviation(String[][] zoneStrings, int simpleDateFormatZoneIndex,
int defaultZoneIndex, String abbreviation) {
String[] simpleDateFormatZoneString = zoneStrings[simpleDateFormatZoneIndex];
if (isAbbreviationMatchZoneString(simpleDateFormatZoneString, abbreviation)) {
return simpleDateFormatZoneIndex;
}
String[] defaultZoneString = zoneStrings[defaultZoneIndex];
if (isAbbreviationMatchZoneString(defaultZoneString, abbreviation)) {
return defaultZoneIndex;
}
for (int i = 0; i < zoneStrings.length; i++) {
if (isAbbreviationMatchZoneString(zoneStrings[i], abbreviation)) {
return i;
}
}
return -1;
}
}
In my environment, the SimpleDateFormat zone and TimeZone.getDefault() are both
Asia/Shanghai, so if the input is CST, the zone interpreted will be China Standard Time instead of Central Standard Time or Cuba Standard Time
It can be done through zoneId.getDisplayName(SHORT, ENGLISH)
I have been working with timezone conversions lately and am quite astonished by the result i get.Basically, i want to convert a date from one timezone into another. below is the code, conversions working fine, but what i have observed while debugging is, the date is not converted unless i call Calendar#get(Calendar.FIELD).
private static void convertTimeZone(String date, String time, TimeZone fromTimezone, TimeZone toTimeZone){
Calendar cal = Calendar.getInstance(fromTimezone);
String[] dateSplit = null;
String[] timeSplit = null;
if(time !=null){
timeSplit = time.split(":");
}
if(date!=null){
dateSplit = date.split("/");
}
if(dateSplit !=null){
cal.set(Calendar.DATE, Integer.parseInt(dateSplit[0]));
cal.set(Calendar.MONTH, Integer.parseInt(dateSplit[1])-1);
cal.set(Calendar.YEAR, Integer.parseInt(dateSplit[2]));
}
if(timeSplit !=null){
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeSplit[0]));
cal.set(Calendar.MINUTE, Integer.parseInt(timeSplit[1]));
}
// System.out.println("Time in " + fromTimezone.getDisplayName() + " : " + cal.get(Calendar.DATE) +"/"+ (cal.get(Calendar.MONTH)+1)+"/"+ cal.get(Calendar.YEAR) +" " + ((cal.get(Calendar.HOUR_OF_DAY)<10) ? ("0"+cal.get(Calendar.HOUR_OF_DAY) ): (cal.get(Calendar.HOUR_OF_DAY)))
// +":" + (cal.get(Calendar.MINUTE)<10 ? "0"+cal.get(Calendar.MINUTE) : cal.get(Calendar.MINUTE)) );
cal.setTimeZone(toTimeZone);
System.out.println("Time in " + toTimeZone.getDisplayName() + " : " + cal.get(Calendar.DATE) +"/"+ (cal.get(Calendar.MONTH)+1)+"/"+ cal.get(Calendar.YEAR) +" " + ((cal.get(Calendar.HOUR_OF_DAY)<10) ? ("0"+cal.get(Calendar.HOUR_OF_DAY) ): (cal.get(Calendar.HOUR_OF_DAY)))
+":" + (cal.get(Calendar.MINUTE)<10 ? "0"+cal.get(Calendar.MINUTE) : cal.get(Calendar.MINUTE)) );
}
public static void main(String[] args) throws ParseException {
convertTimeZone("23/04/2013", "23:00", TimeZone.getTimeZone("EST5EDT"), TimeZone.getTimeZone("GB"));
}
Expected Output: Time in Greenwich Mean Time : 24/4/2013 04:00
Output i got when i comment sysout 1: Time in Greenwich Mean Time : 23/4/2013 23:00
If i un-comment sysout1 i get the expected valid output.
Any help is appreciated
The internal representation of the given date is not evaluated until really needed, that is until you try to access it by those getters. However the best way of parsing dates is through SimpleDateFormat.
EDIT (added for summarize the comments below and to better clarify my answer).
Calendar works this way for better efficiency: instead of recalculate everithing each time you call a setter, it waits until you call a getter.
Calendar should be used mainly for date calculations (see add() and roll()), but you are using it for parsing and formatting: these tasks are better accomplished with SimpleDateFormat, that's why I say that your usage of Calendar is not elegant.
See this example:
private static void convertTimeZone(String date, String time,
TimeZone fromTimezone, TimeZone toTimeZone) throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm");
df.setTimeZone(fromTimezone);
Date d = df.parse(date + " " + time);
df.setTimeZone(toTimeZone);
System.out.println("Time in " + toTimeZone.getDisplayName() + " : " +
df.format(d));
}
I have reimplemented your method using SimpleDateFormat only. My method is smaller, there is no splitting logic (it's hidden in parse()), and also the output is handled in a simpler way. Furthermore the date format is expressed in a compact and standard way that can be easily internationalized using a ResourceBundle.
Also note that the timezone conversion is just a formatting task: the internal representation of the parsed date does not change.
The answer is partly explained in a commented section in setTimeZone():
Consider the sequence of calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST. More
generally, a call to setTimeZone() affects calls to set() BEFORE AND
AFTER it up to the next call to complete().
In other words, the sequence
Set Calendar time
Change TimeZone
is interpreted to mean "Use this time in this new time zone", while the sequence
Set Calendar time
Get the time (or some part of it)
Change Time Zone
will be interpreted as "Use this time in the old time zone, then change it".
So, in your case, to get the behavior you are hoping for, you will need to call get(), or any other method that internally calls complete() inside the Calendar, before you change the time zone.
Are you restricted to using TimeZone and Calendar? If not I suggest using the excellent Library JodaTime which makes handling time zones much easier.
Your example would then look like this:
public static void convertTimeZoneJoda(String date, String time, DateTimeZone fromTimezone, DateTimeZone toTimeZone) {
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyyHH:mm").withZone(fromTimezone);
DateTime dt = dtf.parseDateTime(date+time).withZone(toTimeZone);
System.out.println("Time in " + toTimeZone.getID() + " : " + dt.toString());
}
public static void main(String[] args) {
convertTimeZoneJoda("23/04/2013", "23:00", DateTimeZone.forID("EST5EDT"), DateTimeZone.forID("GMT"));
}
JodaTime also allows for convenient conversation between TimeZone and DateTimeZone.
To add to #Pino's answer, the reason why get() doesn't return an updated time is because setTimeZone() simply doesn't update the fields, just sets areAllFieldsSet to false, which is useless since get() doesn't check for it. If you ask me, this is very poorly coded from SUN's part. Here is the competent code from Calendar:
setTimeZone():
public void setTimeZone(TimeZone value){
zone = value;
sharedZone = false;
areAllFieldsSet = areFieldsSet = false;
}
get():
protected final int internalGet(int field){
return fields[field];
}
This code works for me to convert to UTC:
create a Calendar Object with UTC time zone
Calendar utcTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
set the point in time in the UTC Calendar object from the "any time zone" Calendar object
utcTime.setTimeInMillis(myCalendarObjectInSomeOtherTimeZone.getTimeInMillis());
utcTime will now contain the same point in time as myCalendarObjectInSomeOtherTimeZone converted to UTC.
I have a Java app that needs to be cognizant of the time zone. When I take a Unix epoch time and try to convert it into a timestamp to use for an Oracle SQL call, it is getting the correct timezone, but the timezone "useDaylightTime" value is not correct, i.e., it is currently returning "true", when we are NOT in DST (I am in Florida in TZ "America/New_York").
This is running on Red Hat Linux Enterprise 6, and as far as I can tell, it is correctly set up for the timezone, e.g. 'date' returns:
Wed Nov 28 12:30:12 EST 2012
I can also see, using the 'zdump' utility, that the current value for 'isdst' is 0.
My Java version is 1.6.0_31.
I have Googled this and seen the numerous issues this has caused, but many of them simply say to set the TZ manually, but my issue is not the TZ, but the fact that the default TZ has the "isDaylight" set to 'true'. I believe this is causing my query to return data that is one hour off (I can see that it is).
Here is a simple code piece I have run to try and reproduce this in the simplest way possible:
public class TZdefault {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis()/1000;
Calendar start = Calendar.getInstance();
start.setTimeInMillis(startTime);
start.setTimeZone(TimeZone.getDefault());
System.out.println("Start UTC: " + start + "ms: " + start.getTimeInMillis());
System.out.println("use daylight: " + start.getTimeZone().useDaylightTime());
} // end main
} // end class
One final thing. If in my code I set the TZ to "EST", it of course does return a TZ with 'isDaylight' set to False. But that is not a good solution.
I wanted to add some more detail that I had been hoping to hide.
I have records in an Oracle 11g database that use TIMESTAMP with TIMEZONE fields. I am simply doing JDBC queries where two of the parameters are using BETWEEN a start timestamp and end timestamp.
When I query this table, I am using a prepared statement that is using a Calendar entry, the sole purpose of which was to try and manipulate the timezone. The bottom line is that I am doing a pstmt.setTimestamp() call using the 'getTimeInMillis' method for the start and end time after the "default" timezone was applied. The log output shows that in fact it is putting in the correct milliseconds, but the returned SQL results are clearly off by one hour exactly!
I am still trying to verify that there is not an issue on the data insertion side as well.
But I have a lot of debug information, and it looks like I am asking for the correct time in my JDBC query.
the timezone useDaylightTime value is not correct, i.e., it is currently returning "true", when we are NOT in DST
I think you're confusing useDaylightTime with inDaylightTime. The former tells you whether there is a transition between daylight time and standard time in the future, not which side of that transition you're on. For example, it returns false for Chinese time zones because China does not adjust for daylight savings time, but it returns true for most US time zones because most US states (except Arizona) do observe daylight savings time.
inDaylightTime
public abstract boolean inDaylightTime(Date date)
Queries if the given date is in Daylight Saving Time in this time zone.
vs
useDaylightTime
public abstract boolean useDaylightTime()
Queries if this TimeZone uses Daylight Saving Time.
If an underlying TimeZone implementation subclass supports historical and future Daylight Saving Time schedule changes, this method refers to the last known Daylight Saving Time rule that can be a future prediction and may not be the same as the current rule. Consider calling observesDaylightTime() if the current rule should also be taken into account.
If you want to disable daylight saving calculation, then you must set your timezone to EST. Else otherwise time will be calculated based on default time zone set for AMERICA/NEW_YORK
TimeZone zoneEST = TimeZone.getTimeZone("EST");
System.out.println(zoneEST.getDSTSavings()); //0 hour
System.out.println(zoneEST.getRawOffset()); //5 hour
TimeZone.setDefault(zoneEST);
System.out.println("");
TimeZone zoneNY = TimeZone.getTimeZone("America/New_York");
System.out.println(zoneNY.getDSTSavings()); // 1 hour
System.out.println(zoneNY.getRawOffset()); // 5 hour
I have found a way to ensure the daylight saving is ignored
TimeZone tz = TimeZone.getTimeZone("GMT");
TimeZone.setDefault(tz);
GregorianCalendar calendar;
calendar = new GregorianCalendar();
Set the timezone before you create your GregorianCalendar object
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class TimeZoneTest {
public static void main(String[] argv) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("dd-M-yyyy hh:mm:ss a");
String dateInString = "22-01-2015 10:15:55 AM";
Date date = formatter.parse(dateInString);
TimeZone tz = TimeZone.getDefault();
// From TimeZone Asia/Singapore
System.out.println("TimeZone : " + tz.getID() + " - " + tz.getDisplayName());
System.out.println("TimeZone : " + tz);
System.out.println("Date : " + formatter.format(date));
// To TimeZone America/New_York
SimpleDateFormat sdfAmerica = new SimpleDateFormat("dd-M-yyyy hh:mm:ss a");
TimeZone tzInAmerica = TimeZone.getTimeZone("America/New_York");
sdfAmerica.setTimeZone(tzInAmerica);
String sDateInAmerica = sdfAmerica.format(date); // Convert to String first
Date dateInAmerica = formatter.parse(sDateInAmerica);
System.out.println("\nTimeZone : " + tzInAmerica.getID() +
" - " + tzInAmerica.getDisplayName());
System.out.println("TimeZone : " + tzInAmerica);
System.out.println("Date (String) : " + sDateInAmerica);
System.out.println("Date (Object) : " + formatter.format(dateInAmerica));
}
}