How to save DateTime as Date with TimeZone offset included? - java

I have some DateTime including TimeZone Europe/Vienna (+0200). It is fetched by this method:
settlementService.getPendingPeriodStart()
and look with toString like this:
2012-06-01T00:00:00.000+02:00
Now I want to save this date 2012-06-01 as java.util.Date, so I tried something like this:
transactionDate = settlementService.getPendingPeriodStart().withTime(0, 0, 0, 0).toDate();
But the result is this:
Thu May 31 22:00:00 UTC 2012
What's the best way to save the DateTime result as Date including the TimeZone offset, so transactionDate should be 2012-06-01. I could tinker with GregorianCalendar, but that's not what I like. This ought to be easier, isn't it?
By the way (if this isn't clear). The local system runs on UTC. That's why the result is Thu May 31 22:00:00 UTC 2012.

Unfortunately, the accepted answer is misguiding. As a matter of fact,
2012-06-01T00:00:00.000+02:00 = 2012-05-31T22:00:00Z
The Z on the right-hand side is the timezone designator for zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
Writing 2012-06-01T00:00:00.000+02:00 as 2012-06-01, although a matter of just a function call, is dangerous for any business logic that depends on the timezone because it may have a different date in a timezone with a different offset value e.g. as shown above. 2012-06-01 is just a LocalDate which should be used to track events like birth date, wedding date etc.
java.time
The legacy date-time API (java.util date-time types and their formatting type, SimpleDateFormat etc.) is outdated and error-prone. It is recommended to stop using it completely and switch to java.time, the modern date-time API*.
Also, quoted below is a notice at the Home Page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time, the modern API:
How to parse the given date-time string:
The given date-time string has a timezone offset and therefore it should be parsed to OffsetDateTime. Since the modern date-time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the date-time string conforms to the ISO 8601 standards.
OffsetDateTime odt = OffsetDateTime.parse("2012-06-01T00:00:00.000+02:00"); // 2012-06-01T00:00+02:00
How to get the date-time out of it in UTC:
There are multiple ways. The simplest way is to convert it into an Instant which represents an instantaneous point on the timeline which is in UTC.
Instant instant = odt.toInstant(); // 2012-05-31T22:00:00Z
Alternatively,
OffsetDateTime odtUtc = odt.withOffsetSameInstant(ZoneOffset.UTC); // 2012-05-31T22:00Z
How to get java.util.Date out of it:
If at all, you need an instance of java.util.Date from the instance of OffsetDateTime, you can use Date#from(Instant instant).
Date date = Date.from(instant); // Thu May 31 23:00:00 BST 2012 <--In my timezone
Note that a java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it e.g.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
System.out.println(sdf.format(date)); // 2012-05-31T22:00:00.000Z
How to get the date part out of it:
As I have already explained, it is dangerous for any business logic that depends on the timezone. However, it's just a matter of a simple function call.
LocalDate localDate = odt.toLocalDate(); // 2012-06-01
Learn more about java.time, the modern date-time API* from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

A date and the time zone of the user are 2 different things. One is a date, the other is a preference, or a presentation parameter.
Don't try to store them in the same field.
Consider that it wouldn't be even possible to store them together efficiently (without losing precision) as a date can be stored in a long only because it's specified that it's an UTC date.
You can save the time zone as an offset (a frequent recommendation is to keep the minutes in this offset ([hh]:[mm]) because of very small cases).

I think I found a solution. (If you know a better solution, just let me know)
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
DateTime dateTimeWithTimeZone = new DateTime(DateTimeZone.forID("Europe/Vienna")).withDate(2012, 06, 01).withTime(0, 0, 0, 0);
Date dateWithTimeZoneIncluded = dateTimeWithTimeZone.toLocalDate().toDate();
System.out.println(dateFormat.format(dateWithTimeZoneIncluded));
The result is 2012-06-01 as expected.

Related

How to Pick Timezone from ISO 8601 format String into a Calendar instace

As an input I have a string which is a String in ISO 8601 to represent date. For example:
"2017-04-04T09:00:00-08:00"
The last part of String, which is "-08:00" denotes TimeZone Offset. I convert this string into a Calendar instance as shown below:
Calendar calendar = GregorianCalendar.getInstance();
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(iso8601Date);
calendar.setTime(date);
iso8601Date is "2017-04-04T09:00:00-08:00"
But this does not pick timezone and if I get timezone from Calendar instance, it gives currently set instance of the laptop and does not pick up timestamp from ISO 8601 String. I check for timezone via calendar instance as:
calendar.getTimeZone().getDisplayName()
Can someone show how to pick timezone also in the Calendar instance?
tl;dr
OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" )
Details
The last part of String which is "-08:00" denotes TimeZone Offset.
Do not confuse offset with time zone.
The -08:00 represents an offset-from-UTC, not a time zone. A time zone is a history of various offsets used in the past, present, and future by the people of a particular region. A time zone is named with a continent, slash, and region such as America/Los_Angeles or Pacific/Auckland or Asia/Kolkata.
You are using troublesome old date-time classes now supplanted by the java.time classes. For Android, see the ThreeTen-Backport and ThreeTenABP projects.
Your input indicates only offset but not zone. So we parse as a OffsetDateTime.
OffsetDateTime odt = OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" ) ;
If you are absolutely certain of the intended time zone, assign it.
ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant() ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
When you create a Calendar, it takes the JVM's default timezone. And when you parse a String to a Date, it just sets one value: the number of milliseconds since epoch (1970-01-01T00:00Z). A Date doesn't have any timezone information, just this milliseconds value. So you need to set the timezone in the calendar.
In your formatter, you're treating Z as a literal, because it's inside quotes ('Z'). This ignores the offset and gets the date in the JVM default timezone (which will have a different value if the corresponding offset is not -08:00).
In JDK >= 7, you can use the X pattern to parse the offset:
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.US).parse(iso8601Date);
But this doesn't set the timezone in the calendar (it will still use the JVM's default). So, a "better" way is to strip the offset from the input and handle it separately:
Calendar calendar = GregorianCalendar.getInstance();
String iso8601Date = "2017-04-04T09:00:00-08:00";
// get the offset (-08:00)
String offset = iso8601Date.substring(19);
TimeZone tz = TimeZone.getTimeZone("GMT" + offset);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
// set the offset in the formatter
sdf.setTimeZone(tz);
// parse just date and time (without the offset)
Date date = sdf.parse(iso8601Date.substring(0, 19));
// set the offset in the calendar
calendar.setTimeZone(tz);
calendar.setTime(date);
With this, the calendar will have the offset -08:00 set. As #BasilBourque's answer already said, -08:00 is an offset, not a timezone (the TimeZone class treats offsets just like they were timezones, which is a workaround/bad design choice).
Java new Date/Time API
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.
In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. You'll also need the ThreeTenABP to make it work (more on how to use it here).
#BasilBourque's answer already tells you about OffsetDateTime. But to convert to a Calendar, you can use a org.threeten.bp.ZonedDateTime and convert it using the org.threeten.bp.DateTimeUtils class:
String iso8601Date = "2017-04-04T09:00:00-08:00";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601Date);
Calendar cal = DateTimeUtils.toGregorianCalendar(zdt);
The calendar will be already set with the -08:00 offset.
If you want to get the timezone from the offset, I'm afraid it's not that simple. More than one timezone can use the same offset, so you can't know for sure which timezone to use (the best you can do is to get a list of possible candidates).
java.util.Date
Just a more detailed note about java.util.Date. This link explains a lot about it, so I really recommend you to read it.
As already said above, a Date has no timezone information. It just keeps the number of milliseconds since epoch (which is 1970-01-01T00:00Z, or January 1st 1970 at midnight in UTC).
This value is the same everywhere in the world. Example: at the moment I'm writing this, the millis value for the current time is 1504632865935. This number is the same for anyone in the world who gets the current time at the same instant I did, regardless of what timezone they're using.
What is different is the local date and time that corresponds to this millis value. In UTC, it corresponds to 2017-09-05T17:34:25.935Z, in New York, the date is the same (September 5th 2017) but the time is different (13:34), and in Tokyo is September 6th 2017 at 02:34 AM.
Although the Date object is the same (because its millis value is 1504632865935 for everyone), the corresponding date and time changes according to the timezone used.
People tend to think that a Date has a timezone because when printing it (with System.out.println or by loggging) or when inspecting in a debugger, it implicity uses the toString() method, and this converts the date to the JVM's default timezone (and it also prints the zone name). This gives the impression that a Date has a format and a timezone set to it, but it doesn't.
One Key understanding I want to share from Hugo's answer and my further search is following. Please correct me if I am wrong:
Date does not care about timezone. It represents milliseconds passed since epoch.
Regarding finding the Timezone from provided ISO 8061 format is there, Date class can not tell that and we have to use some alternate methods as specified by #Hugo and #Basil Bourque.

File lastModified() returns Wed Dec 31 19:00:00 EST 1969

I was doing some testing with files like this:
public Date findFileDate(){
File file = new File(filePath);
Date date = new Date(file.lastModified());
return date;
}
When I print date it says: Wed Dec 31 19:00:00 EST 1969. After some research I found that is my "time since the Unix Epoch" according to my time zone, but I am confused why I would get this output when no file exists at my filePath. Why would it not return null or 0?
No, file.lastModified() is returning 0. That's the Unix epoch
In your particular time zone (Eastern US by the looks of it), local time at the Unix epoch was 5 hours behind UTC, so it was 7pm on December 31st 1969.
To confirm this, just separate your Date declaration and assignment into two:
long lastModifiedMillis = file.lastModified();
Date date = new Date(lastModifiedMillis);
Now if you examine lastModifiedMillis I'm sure you'll find a value of 0, as documented:
Returns
A long value representing the time the file was last modified, measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), or 0L if the file does not exist or if an I/O error occurs
java.time
The java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.
Note that the legacy date-time API (java.util date-time types and their formatting API, SimpleDateFormat) is outdated and error-prone. It is recommended to stop using it completely and switch to java.time, the modern date-time API*.
Demo using java.time, modern date-time API:
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Main {
public static void main(String args[]) {
Instant instant = Instant.ofEpochMilli(0);
System.out.println(instant);
// If you need the corresponding date-time representing your timezone
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of("America/New_York"));
System.out.println(zdt);
}
}
Output:
1970-01-01T00:00:00Z
1969-12-31T19:00-05:00[America/New_York]
An Instant represents an instantaneous point on the time-line. The Z in the output stands for Zulu which represents UTC (timezone offset of +00:00 hours).
Learn more about the the modern date-time API* from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Unparseable date: "2011-12-08T02:01:02+01:00"

Following code throws the exception when try to parse from the string (2011-12-08T02:01:02+01:00):
image.setLastUpdated(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz")
.parse(imageElement.getAttribute("lastUpdate")))
I have also tried the following format:
image.setLastUpdated(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.parse(imageElement.getAttribute("lastUpdate")));
But I get the same exception:
java.text.ParseException: Unparseable date: "2011-12-08T02:01:02+01:00"
at java.text.DateFormat.parse(DateFormat.java:357)
at com.discavo.ImportManagerGiata.parseImageXML(ImportManagerGiata.java:204)
You need X as the specifier for the UTC offset as it's in ISO-8601 format, as per the SimpleDateFormat documentation.
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Also, quoted below is a notice from the home page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time, the modern Date-Time API:
The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards. Your Date-Time string conforms to the ISO 8601 format.
Demo:
import java.time.Instant;
import java.time.OffsetDateTime;
public class Main {
public static void main(String[] args) {
OffsetDateTime odt = OffsetDateTime.parse("2011-12-08T02:01:02+01:00");
System.out.println(odt);
// ########### In case you need Instant ###########
Instant instant = odt.toInstant();
System.out.println(instant);
// Java-12 onwards, you can parse the ISO-8601 compliant Date-Time with timezone
// information directly into Instant
instant = Instant.parse("2011-12-08T02:01:02+01:00");
System.out.println(instant);
}
}
Output:
2011-12-08T02:01:02+01:00
2011-12-08T01:01:02Z
2011-12-08T01:01:02Z
ONLINE DEMO
An Instant represents an instantaneous point on the timeline in UTC. The Z in the output is the timezone designator for a zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
Note: For any reason, if you need to convert this object of Instant to an object of java.util.Date, you can do so as follows:
Date date = Date.from(instant);
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
The answer by Jon Skeet is correct. But FYI…
No problem in Joda-Time
The problem presented by this question does not exist if using Joda-Time rather than the mess that is the java.util.Date/Calendar classes bundled with Java.
Joda-Time 2.3 has a variety of built-in parsers, so no need to specify a format in this case. Use methods on the ISODateTimeFormat class to retrieve a variety of formatters designed to parse variations of ISO 8601 formatted strings.
The question fails to account for time zone. So I am showing all three possibilities:
You want the time converted to a specific time zone.Denver in this example.
You want the time adjusted to UTC, for no time offset.Common for storage or further date-time work.
You want to preserve this date-time with its offset.Not usually a good idea, as you have neither the benefit of UTC (no offset and no need to worry about Daylight Saving Time DST or other issues), nor the benefit of a named time zone with rules to handle DST or other issues.
For Desired Time Zone
// For desired time zone. Arbitrarily using 'Denver' as named time zone for this example.
DateTimeZone denverTimeZone = DateTimeZone.forID( "America/Denver" );
org.joda.time.DateTime dateTimeInDenver = org.joda.time.format.ISODateTimeFormat.dateTimeNoMillis().withZone(denverTimeZone).parseDateTime( "2011-12-08T02:01:02+01:00" );
System.out.println( "dateTimeInDenver: " + dateTimeInDenver );
When run…
dateTimeInDenver: 2011-12-07T18:01:02.000-07:00
For UTC
// For UTC (no offset).
org.joda.time.DateTime dateTimeInUTC = org.joda.time.format.ISODateTimeFormat.dateTimeNoMillis().withZoneUTC().parseDateTime( "2011-12-08T02:01:02+01:00" );
System.out.println( dateTimeInUTC );
When run…
2011-12-08T01:01:02.000Z
For Offset As Parsed
// For offset as parsed.
// Least beneficial, as no rules for Daylight Saving Time (DST) or other anomalies can be applied.
org.joda.time.DateTime dateTimeWithOffsetParsed = org.joda.time.format.ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed().parseDateTime( "2011-12-08T02:01:02+01:00" );
System.out.println( "dateTimeWithOffsetParsed: " + dateTimeWithOffsetParsed );
When run…
dateTimeWithOffsetParsed: 2011-12-08T02:01:02.000+01:00

Working with various Calendar TimeZone in Java (without using Joda Time)

I was looking for a way to get current time in various timezones based on an user input. I know I could use Joda Time! but is that the only way?
Isn't there an option in Java for doing this? I tried the following code which gives the same output for all 3 sysouts.
Calendar pst = Calendar.getInstance(TimeZone.getTimeZone("PST"));
System.out.println("PST " + pst.getTime());
Calendar ist = Calendar.getInstance(TimeZone.getTimeZone("Asia/Calcutta"));
System.out.println("IST " + ist.getTime());
Calendar utc = Calendar.getInstance(TimeZone.getTimeZone("Etc/UTC"));
System.out.println("UCT " + utc.getTime());
What am I missing here to get current time in other timezones?
Yes, that would show the same value in every case (or milliseconds apart) because the three calendars all refer to the same instant in time (execution time notwithstanding) and that's all that a java.util.Date represents. That's the result of Calendar.getTime().
However, the Calendar itself does know about time zones, and that will be reflected when you use Calendar.get etc. It will also be used when you use a SimpleDateFormat, where you can specify a particular time zone.
// Specify whatever format you want - bear in mind different locales etc
SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
format.setTimeZone(calendar.getTimeZone());
String text = format.format(calendar.getTime());
It's not clear exactly what you're trying to do, but basically you need to be aware of which types are time zone aware, and which aren't. It's really important to understand that a java.util.Date doesn't have a format, a calendar system or a time zone: it's just the number of milliseconds since the Unix epoch.
As Jon pointed out the method getTime() is returning a java.util.Date object which is just a millisecond value and not timezone aware.
If you are just looking at printing the times then you can use the calendar and manually get the fields you want like
System.out.println(utc.get(Calendar.HOUR_OF_DAY) + ":" + utc.get(Calendar.MINUTE))
This would need some formatting for a minute < 10 to display the 0
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Also, quoted below is a notice from the home page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time, the modern Date-Time API:
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class Main {
public static void main(String[] args) {
Instant now = Instant.now();
System.out.println(now);
ZonedDateTime zdtLos = now.atZone(ZoneId.of("America/Los_Angeles"));
ZonedDateTime zdtIndia = now.atZone(ZoneId.of("Asia/Kolkata"));
ZonedDateTime zdtUtc = now.atZone(ZoneOffset.UTC);
System.out.println(zdtLos);
System.out.println(zdtIndia);
System.out.println(zdtUtc);
}
}
Output from a sample run:
2021-07-26T12:39:17.413671-07:00[America/Los_Angeles]
2021-07-27T01:09:17.413671+05:30[Asia/Kolkata]
2021-07-26T19:39:17.413671Z
ONLINE DEMO
An Instant represents an instantaneous point on the timeline, normally represented in UTC time. The Z in the output is the timezone designator for a zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
For any reason, if you need to convert this object of Instant to an object of java.util.Date, you can do so as follows:
Date date = Date.from(instant);
Learn more about the modern Date-Time API from Trail: Date Time.
Never use the 3-letter abbreviated timezone ID
Given below is an excerpt from the documentation page of TimeZone:
For compatibility with JDK 1.1.x, some other three-letter time zone
IDs (such as "PST", "CTT", "AST") are also supported. However, their
use is deprecated because the same abbreviation is often used for
multiple time zones (for example, "CST" could be U.S. "Central
Standard Time" and "China Standard Time"), and the Java platform can
then only recognize one of them.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

How to handle calendar TimeZones using Java?

I have a Timestamp value that comes from my application. The user can be in any given local TimeZone.
Since this date is used for a WebService that assumes the time given is always in GMT, I have a need to convert the user's parameter from say (EST) to (GMT). Here's the kicker: The user is oblivious to his TZ. He enters the creation date that he wants to send to the WS, so what I need is:
User enters: 5/1/2008 6:12 PM (EST)
The parameter to the WS needs to be: 5/1/2008 6:12 PM (GMT)
I know TimeStamps are always supposed to be in GMT by default, but when sending the parameter, even though I created my Calendar from the TS (which is supposed to be in GMT), the hours are always off unless the user is in GMT. What am I missing?
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
java.util.Calendar cal = java.util.Calendar.getInstance(
GMT_TIMEZONE, EN_US_LOCALE);
cal.setTimeInMillis(ts_.getTime());
return cal;
}
With the previous Code, this is what I get as a result (Short Format for easy reading):
[May 1, 2008 11:12 PM]
public static Calendar convertToGmt(Calendar cal) {
Date date = cal.getTime();
TimeZone tz = cal.getTimeZone();
log.debug("input calendar has date [" + date + "]");
//Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
long msFromEpochGmt = date.getTime();
//gives you the current offset in ms from GMT at the current date
int offsetFromUTC = tz.getOffset(msFromEpochGmt);
log.debug("offset is " + offsetFromUTC);
//create a new calendar in GMT timezone, set to this date and add the offset
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.setTime(date);
gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);
log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");
return gmtCal;
}
Here's the output if I pass the current time ("12:09:05 EDT" from Calendar.getInstance()) in:
DEBUG - input calendar has date [Thu Oct 23 12:09:05 EDT 2008]
DEBUG - offset is -14400000
DEBUG - Created GMT cal with date [Thu Oct 23 08:09:05 EDT 2008]
12:09:05 GMT is 8:09:05 EDT.
The confusing part here is that Calendar.getTime() returns you a Date in your current timezone, and also that there is no method to modify the timezone of a calendar and have the underlying date rolled also. Depending on what type of parameter your web service takes, your may just want to have the WS deal in terms of milliseconds from epoch.
Thank you all for responding. After a further investigation I got to the right answer. As mentioned by Skip Head, the TimeStamped I was getting from my application was being adjusted to the user's TimeZone. So if the User entered 6:12 PM (EST) I would get 2:12 PM (GMT). What I needed was a way to undo the conversion so that the time entered by the user is the time I sent to the WebServer request. Here's how I accomplished this:
// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
currentDt.get(Calendar.ERA),
currentDt.get(Calendar.YEAR),
currentDt.get(Calendar.MONTH),
currentDt.get(Calendar.DAY_OF_MONTH),
currentDt.get(Calendar.DAY_OF_WEEK),
currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(issueDate.getTime()));
The code's output is: (User entered 5/1/2008 6:12PM (EST)
Current User's TimeZone: EST
Current Offset from GMT (in hrs):-4 (Normally -5, except is DST adjusted)
TS from ACP: 2008-05-01 14:12:00.0
Calendar Date converted from TS using GMT and US_EN Locale: 5/1/08 6:12 PM (GMT)
You say that the date is used in connection with web services, so I assume that is serialized into a string at some point.
If this is the case, you should take a look at the setTimeZone method of the DateFormat class. This dictates which time zone that will be used when printing the time stamp.
A simple example:
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());
You can solve it with Joda Time:
Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));
Java 8:
LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
ZoneId.of("Canada/Newfoundland"));
It looks like your TimeStamp is being set to the timezone of the originating system.
This is deprecated, but it should work:
cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
The non-deprecated way is to use
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
but that would need to be done on the client side, since that system knows what timezone it is in.
Method for converting from one timeZone to other(probably it works :) ).
/**
* Adapt calendar to client time zone.
* #param calendar - adapting calendar
* #param timeZone - client time zone
* #return adapt calendar to client time zone
*/
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
Calendar ret = new GregorianCalendar(timeZone);
ret.setTimeInMillis(calendar.getTimeInMillis() +
timeZone.getOffset(calendar.getTimeInMillis()) -
TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
ret.getTime();
return ret;
}
Date and Timestamp objects are timezone-oblivious: they represent a certain number of seconds since the epoch, without committing to a particular interpretation of that instant as hours and days.
Timezones enter the picture only in GregorianCalendar (not directly needed for this task) and SimpleDateFormat, which need a timezone offset to convert between separate fields and Date (or long) values.
The OP's problem is right at the beginning of his processing: the user inputs hours, which are ambiguous, and they are interpreted in the local, non-GMT timezone; at this point the value is "6:12 EST", which can be easily printed as "11.12 GMT" or any other timezone but is never going to change to "6.12 GMT".
There is no way to make the SimpleDateFormat that parses "06:12" as "HH:MM" (defaulting to the local time zone) default to UTC instead; SimpleDateFormat is a bit too smart for its own good.
However, you can convince any SimpleDateFormat instance to use the right time zone if you put it explicitly in the input: just append a fixed string to the received (and adequately validated) "06:12" to parse "06:12 GMT" as "HH:MM z".
There is no need of explicit setting of GregorianCalendar fields or of retrieving and using timezone and daylight saving time offsets.
The real problem is segregating inputs that default to the local timezone, inputs that default to UTC, and inputs that really require an explicit timezone indication.
Something that has worked for me in the past was to determine the offset (in milliseconds) between the user's timezone and GMT. Once you have the offset, you can simply add/subtract (depending on which way the conversion is going) to get the appropriate time in either timezone. I would usually accomplish this by setting the milliseconds field of a Calendar object, but I'm sure you could easily apply it to a timestamp object. Here's the code I use to get the offset
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId is the id of the user's timezone (such as EST).
java.time
The modern approach uses the java.time classes that supplanted the troublesome legacy date-time classes bundled with the earliest versions of Java.
The java.sql.Timestamp class is one of those legacy classes. No longer needed. Instead use Instant or other java.time classes directly with your database using JDBC 4.2 and later.
The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = myResultSet.getObject( … , Instant.class ) ;
If you must interoperate with an existing Timestamp, convert immediately into java.time via the new conversion methods added to the old classes.
Instant instant = myTimestamp.toInstant() ;
To adjust into another time zone, specify the time zone as a ZoneId object. Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter pseudo-zones such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
Apply to the Instant to produce a ZonedDateTime object.
ZonedDateTime zdt = instant.atZone( z ) ;
To generate a string for display to the user, search Stack Overflow for DateTimeFormatter to find many discussions and examples.
Your Question is really about going the other direction, from user data-entry to the date-time objects. Generally best to break your data-entry into two parts, a date and a time-of-day.
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;
Your Question is not clear. Do you want to interpret the date and the time entered by the user to be in UTC? Or in another time zone?
If you meant UTC, create a OffsetDateTime with an offset using the constant for UTC, ZoneOffset.UTC.
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;
If you meant another time zone, combine along with a time zone object, a ZoneId. But which time zone? You might detect a default time zone. Or, if critical, you must confirm with the user to be certain of their intention.
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
To get a simpler object that is always in UTC by definition, extract an Instant.
Instant instant = odt.toInstant() ;
…or…
Instant instant = zdt.toInstant() ;
Send to your database.
myPreparedStatement.setObject( … , instant ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Categories

Resources