TimeZone with Calendar confusing results - java

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.

Related

How to add minutes to a time stamp that I already have?

I searched a lot, but I found a way to add or subtract time from the calendar instance which gives the current time.. how do I subtract time from the last modified time of a given file?
UPDATE :
I've been using Java1.4. That is the reason I'm unable to find any methods to do this.
I extracted the modified date as a string. I wanted to convert this string obey I a calendar object so that it's easier for me to apply add () of the calendar object to the time. I've been facing issues with the same. is this approach correct? Could you please assist
NIO and java.time
Path filePath = Paths.get("myFile.txt");
Duration timeToSubtract = Duration.ofMinutes(7);
FileTime lastModified = Files.getLastModifiedTime(filePath);
Instant lastModifiedInstant = lastModified.toInstant();
Instant timeBeforeLastModified = lastModifiedInstant.minus(timeToSubtract);
System.out.println("Time after subtraction is " + timeBeforeLastModified);
Running just now on my computer I got this output:
Time after subtraction is 2017-02-18T03:06:04Z
The Z at the end indicates UTC. Instant::toString (implicitly called when appending the Instant to a string) always generates a string in UTC.
I am using the modern Java NIO API and java.time, the modern Java date and time API. NIO gives us a FileTime in this case denoting the time the file was last modified. In order to do our time math I first convert it to an Instant, which is a central class of java.time. The minus method of an Instant subtracts a Duration, an amount of time, and returns a new Instant object.
Don’t use Calendar. That class was poorly designed and is long outdated.
Link: Oracle tutorial: Date Time explaining how to use java.time.
If you dont have to use calendar, than just use the new Java DateTime API available since Java8 https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html
There you have very nice convenience methods like plus/minus etc.
For example you can simply write
LocalTime now = LocalTime.now();
now.minusHours(2);
Timestamp (as long) you can set in Calendar instance. And after that you can add() time.
public void time() {
long timeStamp = 31415926535L;
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeStamp);
// Substract 1 hour
calendar.add(Calendar.HOUR, -1);
// Add 20 minutes
calendar.add(Calendar.MINUTE, 20);
}
You can use setTime() like this:
yourTimeStamp.setTime(yourTimeStamp.getTime() + TimeUnit.MINUTES.toMillis(minutesToAdd));
Since Java 8, java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat are now legacy. So I edit my codes, remove the legacy classes.
Thanks #Ole V.V. and #Basil Bourque for pointing my problems.
I'm confused about your issue. I guess you want to modify a file's last modified time.
So I write down the codes.
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class Test {
private static final long ONE_SECOND = 1000L;
private static final long ONE_MINUTE = 60L * ONE_SECOND;
public static void main(String[] args) {
File file = new File("test.txt");
if (!file.exists()) {
System.err.println("File doesn't exist.");
return;
}
//get file's last modified, in millisecond
long timestamp = file.lastModified();
System.out.println("File's last modified in millisecond: " + timestamp);
//print time for human, convert to LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
System.out.println("File's last modified in LocalDateTime: " + localDateTime);
//add a minute
timestamp = timestamp + ONE_MINUTE;
//modify file's last modified
boolean isModified = file.setLastModified(timestamp);
if (isModified) {
System.out.println("Update file's last modified successfully.");
System.out.println("File's last modified in millisecond: " + file.lastModified());
//print time for human, convert to LocalDateTime
System.out.println("File's last modified in LocalDateTime: " +
LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), ZoneId.systemDefault()));
} else {
System.err.println("Update file's last modified failed.");
}
}
}
Besides, If you want modify a timestamp, just use +/- operations.
And you can convert timestamp to LocalDateTime, and use LocalDateTime's api to modify time easily.
public void modifyTime() {
//modify timestamp: add one second
long timestamp = System.currentTimeMillis();
timestamp = timestamp + 1000L;
//convert timestamp to LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
//modify LocalDateTime
//add a minute
localDateTime = localDateTime.plusMinutes(1);
//subtract a second
localDateTime = localDateTime.minusSeconds(1);
}
If I misunderstood your idea, please let me know.

Convert time to milliseconds using SimpleDateFormat

I am trying to write a utility function which converts the accepts date,timestamp,milliseconds additional to timestamp and return the time in milliseconds. However, I am getting a parse Exception for that.
Example params:
dateJson: 14.11.2016
timestampJson: 21:04:20
millisecsJson: 244
public static long convertToMillisecs(String dateJson, String timestampJson, String millisecsJson) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy'T'HH:mm:ss.SSS");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String inputString = timestampJson + "." + millisecsJson;
Date date = sdf.parse(dateJson + 'T' + inputString);
return date.getTime();
}
What has to be changed to get the correct parseable date. It is to be noted that I am using a 24 hour clock and I am based in Germany so using UTC in that case is okay ?
14.11.2016 doesn't match dd-MM-yyyy.
And no, Germany is not in the UTC timezone. Use Europe/Berlin.

How to convert this unix time string to java date

I got this time string "2015-07-16T03:58:24.932031Z", I need to convert to a java timestamp, I use the following code, seems the converted date is wrong?
public static void main(String[] args) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");
Date date = format.parse("2015-07-16T03:58:24.932031Z");
System.out.println("date: " + date);
System.out.println("timestamp: " + date.getTime());
}
output:
date: Thu Jul 16 04:13:56 CST 2015
timestamp: 1436991236031
Is my date format wrong?
Thanks in advance!
You don't want to quote the Z, it's a timezone indicator. Instead, use the X format specifier, for an ISO-8601 timezone.
Separately, you may want to pre-process the string a bit, because the part at the end, .932031, isn't milliseconds (remember, there are only 1000ms in a second). Looking at that value, it's probably microseconds (millionths of a second). SimpleDateFormat doesn't have a format specifier for microseconds. You could simply use a regular expression or other string manipulation to remove the last three digits of it to turn it into milliseconds instead.
This (which assumes you've done that trimming) works: Live Copy
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
// Note --------------------------------------------------------^^^^
Date date = format.parse("2015-07-16T03:58:24.932Z");
// Note trimming --------------------------------^
System.out.println("date: " + date);
System.out.println("timestamp: " + date.getTime());

Java getTimeZone() behaving differently for different kind of inputs

For the below code I get different outputs
TimeZone t = TimeZone.getTimeZone("GMT-8"); // Also tried UTC-8 and GMT-8:00
//TimeZone t = TimeZone.getTimeZone("America/Los_Angeles");
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
Date d = null;
Calendar c = Calendar.getInstance(t);
c.set(Calendar.MONTH, Calendar.AUGUST);
c.set(Calendar.DAY_OF_MONTH, 22);
c.set(Calendar.YEAR, 2013);
d = c.getTime();
String s = sdf.format(d);
System.out.println(s + " " + t.inDaylightTime(d));
Output is:
08/22/2013 false
Now America/Los_Angeles is GMT-8 / UTC-8 or PST. But when I change parameter from GMT-8 to America/Los_Angeles,
Output is:
08/22/2013 true
Can't use PST like abbreviations since its deprecated. Also CST can mean both Central Standard time and China Standard Time.
The input I have is like -8, -9 -14 etc. to which I wish to prepend GMT/UTC to know if I can get the DST activation on a given date.
Please guide me in this regard.
The results are correct, when you specify GMT with an offset there is no way to know which country/city is this, so it is not possible to determine if Day Light Time is active or no.
So inDaylightTime() will be false for all time zones specified with GMT and an offset.
CST in Java is Central Standard Time, you can check yourself by this line of code:
System.out.println(TimeZone.getTimeZone("CST").getDisplayName());
China Standard Time zone ID in Java is CTT
Edit
Consider using Joda time and date API for more up-to-date Timezone information
The results are correct, as explained by #iTech. Just to add, you can lookup the timezone ids available as
import java.util.TimeZone;
public class TimeZones {
public static void main(String[] args) {
String[] timezones = TimeZone.getAvailableIDs();
System.out.println(timezones.length);
for (String t : timezones) {
System.out.println(t);
}
}
}
This prints a long list of 600 timezones available.
You can then get the display name for the abbreviated ones as
System.out.println(TimeZone.getTimeZone("IST").getDisplayName());

problems with Java daylight savings time

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

Categories