Converting a Date to a new TimeZone in Java - java

Converting a Java Date object to a specific ("target") time zone is a question that has been asked numerous times before in StackOverflow and the suggested solutions apparently are:
Adjust the time (milliseconds) value of the Date object by the timezone offset
Use SimpleDateFormat (with the timezone) to produce the desired date as a String
However, there seems to be no "plain Java" solution that parses a date string (with or without timezone) into a Date object and then adjusts a corresponding Calendar object to the target timezone, so that the "converted" date behaves in all cases in the correct way (e.g., when using the Calendar object to retrieve the year of the date).
The main issue is that, reasonably, when the SimpleDateFormat pattern used to parse the date string does not have a timezone, the system assumes that the time is in the parsing system's timezone. If, however, the pattern does have a timezone, the time is counted from UTC. So, what is needed is essentially an indication as to whether the original date string corresponds to a pattern with or without a timezone, which apparently cannot be done without analyzing the pattern itself.
The attached source code of the TimezoneDate class demonstrates the discrepancies.
Here is the output from running the main() method for 2 "original dates", the first without and the second with a timezone, when they are converted to UTC on a machine running on Pacific Standard Time (PST):
Original date: 20/12/2012 08:12:24
Formatted date (TZ): 20/12/2012 16:12:24
Formatted date (no TZ): 20/12/2012 08:12:24
Calendar date: 20/12/2012 16:12:24
Expected date: 20/12/2012 08:12:24
Original date: 20/10/2012 08:12:24 +1200
Formatted date (TZ): 19/10/2012 20:12:24
Formatted date (no TZ): 19/10/2012 13:12:24
Calendar date: 19/10/2012 20:12:24
Expected date: 19/10/2012 20:12:24
The correct operation is to have the "Formatted" and "Calendar" strings identical to the "Expected date" for each of the 2 example date strings ("original dates").
Apparently one needs to make a distinction between the case where the date string contains a timezone symbol (TZ) and the case where it does not (no TZ), but this means knowing the SimpleDateFormat pattern beforehand, which is not possible when handling a Date object and not the original string.
So, the question is really about whether a generic "plain Java" (no third party libraries) solution exists that does not require prior knowledge of the pattern and works correctly with the corresponding Calendar object.
Following is the full source code of the TimezoneDate class.
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class TimezoneDate
{
private final Date time;
private final TimeZone timezone;
private final Calendar calendar;
/**
* Creates a wrapper for a {#code Date} object that is converted to the
* specified time zone.
*
* #param date The date to wrap
* #param timezone The timezone to convert to
*/
public TimezoneDate(Date date, TimeZone timezone)
{
this.calendar = TimezoneDate.getPlainCalendar(date, timezone);
this.time = this.calendar.getTime();
this.timezone = timezone;
}
private static Calendar getPlainCalendar(Date date, TimeZone timezone)
{
Calendar calendar = Calendar.getInstance(timezone);
calendar.setTime(date);
return calendar;
}
private static Calendar getAdjustedCalendar(Date date, TimeZone timezone)
{
long time = date.getTime();
time = time + timezone.getOffset(time) - TimeZone.getDefault().getOffset(time);
date = new Date(time);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
public int getYear()
{
return this.calendar.get(Calendar.YEAR);
}
public int getMonth()
{
return (this.calendar.get(Calendar.MONTH)+1);
}
public int getMonthDay()
{
return this.calendar.get(Calendar.DAY_OF_MONTH);
}
public int getHour()
{
return this.calendar.get(Calendar.HOUR_OF_DAY);
}
public int getMinutes()
{
return this.calendar.get(Calendar.MINUTE);
}
public int getSeconds()
{
return this.calendar.get(Calendar.SECOND);
}
public String toCalendarDate() // The date as reported by the Calendar
{
StringBuilder sb = new StringBuilder();
sb.append(this.getMonthDay()).append("/").append(this.getMonth()).
append("/").append(this.getYear()).append(" ").
append(this.getHour()).append(":").append(this.getMinutes()).
append(":").append(this.getSeconds());
return sb.toString();
}
public String toFormattedDate(boolean addTimezone) // The formatted date string
{
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (addTimezone)
{
sdf.setTimeZone(this.timezone);
}
return sdf.format(this.time);
}
public static void main(String[] args) throws Exception
{
// Each data "vector" contains 3 strings, i.e.:
// - Original (example) date
// - SimpleDateFormat pattern
// - Expected date after converting the original date to UTC
String[][] data = new String[][]
{
{"20/12/2012 08:12:24", "dd/MM/yyyy' 'HH:mm:ss", "20/12/2012 08:12:24"},
{"20/10/2012 08:12:24 +1200", "dd/MM/yyyy HH:mm:ss Z", "19/10/2012 20:12:24"}
};
Date originalDate;
TimezoneDate timezoneDate;
SimpleDateFormat format;
TimeZone UTC = TimeZone.getTimeZone("UTC");
for (String[] vector:data)
{
format = new SimpleDateFormat(vector[1]);
originalDate = format.parse(vector[0]);
timezoneDate = new TimezoneDate(originalDate, UTC);
System.out.println();
System.out.println("Original date: " + vector[0]);
System.out.println("Formatted date (TZ): " + timezoneDate.toFormattedDate(true));
System.out.println("Formatted date (no TZ): " + timezoneDate.toFormattedDate(false));
System.out.println("Calendar date: " + timezoneDate.toCalendarDate());
System.out.println("Expected date: " + vector[2]);
}
}
}

Joda-Time
Rather than use Java's Date you should use the Joda-Time DateTime class. This allows you to carry out timezone operations in a very simple fashion, using the withTimezone() and withTimezoneRetainFields() methods depending on your particular requirements.

Related

Converting String to TimeStamp using .valueOf() adds zero at the end of time [duplicate]

The function shown below returns the date, e.g. "Sat Sep 8 00:00 PDT 2010". But I expected to get the date in the following format "yyyy-MM-dd HH:mm". What's wrong in this code?
String date = "2010-08-25";
String time = "00:00";
Also in one laptop the output for,e.g. 23:45 is 11:45. How can I define exactly the 24 format?
private static Date date(final String date,final String time) {
final Calendar calendar = Calendar.getInstance();
String[] ymd = date.split("-");
int year = Integer.parseInt(ymd[0]);
int month = Integer.parseInt(ymd[1]);
int day = Integer.parseInt(ymd[2]);
String[] hm = time.split(":");
int hour = Integer.parseInt(hm[0]);
int minute = Integer.parseInt(hm[1]);
calendar.set(Calendar.YEAR,year);
calendar.set(Calendar.MONTH,month);
calendar.set(Calendar.DAY_OF_MONTH,day);
calendar.set(Calendar.HOUR,hour);
calendar.set(Calendar.MINUTE,minute);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date d = calendar.getTime();
String dateString= dateFormat.format(d);
Date result = null;
try {
result = (Date)dateFormat.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return result;
}
What's wrong in this code?
You seem to be expecting the returned Date object to know about the format you've parsed it from - it doesn't. It's just an instant in time. When you want a date in a particular format, you use SimpleDateFormat.format, it's as simple as that. (Well, or you use a better library such as Joda Time.)
Think of the Date value as being like an int - an int is just a number; you don't have "an int in hex" or "an int in decimal"... you make that decision when you want to format it. The same is true with Date.
(Likewise a Date isn't associated with a specific calendar, time zone or locale. It's just an instant in time.)
How did you print out the return result? If you simply use System.out.println(date("2010-08-25", "00:00") then you might get Sat Sep 8 00:00 PDT 2010 depending on your current date time format setting in your running machine. But well what you can do is:
Date d = date("2010-08-25", "00:00");
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(d));
Just curious why do you bother with this whole process as you can simple get the result by concatenate your initial date and time string.
just use SimpleDateFormat class
See
date formatting java simpledateformat
The standard library does not support a formatted Date-Time object.
The function shown below returns the date, e.g. "Sat Sep 8 00:00 PDT
2010". But I expected to get the date in the following format
"yyyy-MM-dd HH:mm".
The standard Date-Time classes do not have any attribute to hold the formatting information. Even if some library or custom class promises to do so, it is breaking the Single Responsibility Principle. A Date-Time object is supposed to store the information about Date, Time, Timezone etc., not about the formatting. The only way to represent a Date-Time object in the desired format is by formatting it into a String using a Date-Time parsing/formatting type:
For the modern Date-Time API: java.time.format.DateTimeFormatter
For the legacy Date-Time API: java.text.SimpleDateFormat
About java.util.Date:
A java.util.Date object simply represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). Since it does not hold any timezone information, its toString function applies the JVM's timezone to return a String in the format, EEE MMM dd HH:mm:ss zzz yyyy, derived from this milliseconds value. To get the String representation of the java.util.Date object in a different format and timezone, you need to use SimpleDateFormat with the desired format and the applicable timezone e.g.
Date date = new Date(); // In your case, it will be Date date = date("2010-08-25", "00:00");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH);
// sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); // For a timezone-specific value
String strDate = sdf.format(date);
System.out.println(strDate);
Your function, Date date(String, String) is error-prone.
You can simply combine the date and time string with a separator and then use SimpleDateFormat to parse the combined string e.g. you can combine them with a whitespace character as the separator to use the same SimpleDateFormat shown above.
private static Date date(final String date, final String time) throws ParseException {
return sdf.parse(date + " " + time);
}
Note that using a separator is not a mandatory requirement e.g. you can do it as sdf.parse(date + time) but for this, you need to change the format of sdf to yyyy-MM-ddHH:mm which, although correct, may look confusing.
Demo:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Main {
static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH);
public static void main(String[] args) throws ParseException {
Date date = date("2010-08-25", "00:00");
String strDate = sdf.format(date);
System.out.println(strDate);
}
private static Date date(final String date, final String time) throws ParseException {
return sdf.parse(date + " " + time);
}
}
Output:
2010-08-25 00:00
ONLINE DEMO
Switch to java.time API.
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*.
Solution using java.time, the modern Date-Time API:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
LocalDateTime ldt = localDateTime("2010-08-25", "00:00");
// Default format i.e. the value of ldt.toString()
System.out.println(ldt);
// Custom format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm", Locale.ENGLISH);
String strDate = dtf.format(ldt);
System.out.println(strDate);
}
private static LocalDateTime localDateTime(final String date, final String time) {
return LocalDateTime.of(LocalDate.parse(date), LocalTime.parse(time));
}
}
Output:
2010-08-25T00:00
2010-08-25 00:00
ONLINE DEMO
You must have noticed that I have not used DateTimeFormatter for parsing the String date and String time. It is because your date and time strings conform to the ISO 8601 standards. 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.
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.
I'm surprise you are getting different date outputs on the different computers. In theory, SimpleDateFormat pattern "H" is supposed to output the date in a 24h format. Do you get 11:45pm or 11:45am?
Although it should not affect the result, SimpleDateFormat and Calendar are Locale dependent, so you can try to specify the exact locale that you want to use (Locale.US) and see if that makes any difference.
As a final suggestion, if you want, you can also try to use the Joda-Time library (DateTime) to do the date manipulation instead. It makes it significantly easier working with date objects.
DateTime date = new DateTime( 1991, 10, 13, 23, 39, 0);
String dateString = new SimpleDateFormat("yyyy-MM-dd HH:mm").format( date.toDate());
DateTime newDate = DateTime.parse( dateString, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));

Convert GMT/CST based string to Timestamp in java

I m trying to get time from one zone say IST OR ANY other time zone then convert it to GMT TIME zone string. But when I try to get timestamp from GMT based string time I get local timestamp value. Any specific reason why so.
Found this on web.
Calendar calendar = Calendar.getInstance();
TimeZone fromTimeZone = calendar.getTimeZone();
TimeZone toTimeZone = TimeZone.getTimeZone("CST");
calendar.setTimeZone(fromTimeZone);
calendar.add(Calendar.MILLISECOND, fromTimeZone.getRawOffset() * -1);
if (fromTimeZone.inDaylightTime(calendar.getTime())) {
calendar.add(Calendar.MILLISECOND, calendar.getTimeZone().getDSTSavings() * -1);
}
calendar.add(Calendar.MILLISECOND, toTimeZone.getRawOffset());
if (toTimeZone.inDaylightTime(calendar.getTime())) {
calendar.add(Calendar.MILLISECOND, toTimeZone.getDSTSavings());
}
System.out.println(calendar.getTime());
"Any specific reason why so?" Yes, because Java assumes that by default you want to "print out" (render) date values in the local timezone where the JVM is running.
#Before
public void setup() {
sdf = new SimpleDateFormat(Hello.TS_FORMAT);// TS_FORMAT = "yyyyMMdd'T'HHmmssXX";
Calendar cal = sdf.getCalendar();
cal.setTimeZone(TimeZone.getTimeZone(UTC_TIME_ZONE));// UTC_TIME_ZONE = "GMT";
sdf.setCalendar(cal);
...
}
#Test
public void testTimestampFormat03() {
String inboundTimestampText = '20170322T170805-0700';// means inbound is in Pacific Time Zone (17:08:05 on 03/22)
Date dt = sdf.parse(inboundTimestampText);
String defaultFormat = dt.toString();// default locale is Central Time Zone (19:08:05 on 03/22)
String actualFormat = sdf.format(dt);
String expectedFormat = inboundTimestampText.replace('T17', 'T00');
expectedFormat = expectedFormat.replace('0322', '0323');// expected Time Zone is UTC (00:08:05 on 03/23)
expectedFormat = expectedFormat.replace('-', 'Z');
assertEquals(expectedFormat, actualFormat + '0700');
}
You have to specify the timezone you want the date value to "render" in. Basically, you need to use "the same" formatter to print out the date formatter.format(aDate) that you used to read in the date string formatter.parse(aDtaeString).

Wrong last day of month

Where is some function to get the last day of month in my service?
DateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
Date date = format.parse(stringDate);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.MONTH, 1);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.DATE, -1);
Date lastDayOfMonth = calendar.getTime();
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(lastDayOfMonth);
So, this method correctly works elsewhere, but in US last day is always 29 (last day - 1)
stringDate is date in format "yyyy-MM-dd"
I believe this problem is due to Day Light saving time in US.
You can change this by setting the Timezone for Calendar to different timezone.
Related question: Adding days with java.util.Calendar gives strange results
Java Date has very poor API. Instead of this I would recommend you to use Joda Time.
In Joda it would look like this:
LocalDate endOfMonth = date.dayOfMonth().withMaximumValue();
If you don't have Java 8, this is very compact with JodaTime.
import org.joda.time.DateTime;
public class SoLastDay {
public DateTime lastDay(final String yyyy_MM_dd) {
DateTime givenDate = new DateTime(yyyy_MM_dd);
return givenDate.dayOfMonth().withMaximumValue();
}
}
And a small test...
#Test
public void testLastDay() throws Exception {
SoLastDay soLastDay = new SoLastDay();
String date1 = "2015-01-27";
System.out.printf("Date %s becomes %s.\n", date1, soLastDay.lastDay(date1).toString("yyyy-MM-dd"));
String date2 = "2015-02-02";
System.out.printf("Date %s becomes %s.\n", date2, soLastDay.lastDay(date2).toString("yyyy-MM-dd"));
}
And the test results:
Date 2015-01-27 becomes 2015-01-31.
Date 2015-02-02 becomes 2015-02-28.
If you do have Java 8, you might use code like this:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
public class SoLastDayJava8 {
static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public LocalDate lastDay(final String yyyy_MM_dd) {
LocalDate givenDate = LocalDate.parse(yyyy_MM_dd, formatter);
return givenDate.with(TemporalAdjusters.lastDayOfMonth());
}
}
The test code changes just a bit.
public class SoLastDayJava8Test {
#Test
public void testLastDay() throws Exception {
SoLastDayJava8 soLastDay = new SoLastDayJava8();
String date1 = "2015-01-27";
System.out.printf("Date %s becomes %s.\n", date1, soLastDay.lastDay(date1));
String date2 = "2015-02-02";
System.out.printf("Date %s becomes %s.\n", date2, soLastDay.lastDay(date2));
}
}
But the results are the same.
Date 2015-01-27 becomes 2015-01-31.
Date 2015-02-02 becomes 2015-02-28.
You are messing with the TimeZones.
When you execute Date date = format.parse(stringDate); you are creating a Date object with the TimeZone of the DateFormat object. Theoretically if the TimeZone is the same for all your DateFormat and Calendar objects, you should be fine. Check if they are coherent with the getTimeZone() method.
If the TimeZone of the first DateFormat is wrong (e.g. is your TimeZone or UTC or GMT), you'll get a UTC-008 conversion in the second TimeZone (and in the Calendar) resulting in the missing day since you start from midnight.
Judging from your code is the stringDate itself that has been wrongly converted somewhere else...

Difference between date in miliseconds and its Calendar representation

I have two functions which convert a date String to a date in milliseconds:
public static long convertYYYYMMDDtoLong(String date) throws ParseException {
SimpleDateFormat f = new SimpleDateFormat("yyyy-mm-dd");
Date d = f.parse(date);
long milliseconds = d.getTime();
return milliseconds;
}
If I run this function I get the following result:
long timeStamp = convertYYYYMMDDtoLong("2014-02-17");
System.out.println(timeStamp);
It prints:
1389909720000
Now, if I run the following code:
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(timeStamp);
System.out.println(cal.getTime());
It prints out:
Fri Jan 17 00:02:00 IST 2014
Why is my date shifted by one month? What is wrong?
P.S: My problem is that I need to map the date, represented as long, to another third party API which accepts Calendar format only.
You're using mm, which is minutes, not months. You want yyyy-MM-dd as your format string.
It's not clear why you're not returning a Calendar directly from your method, mind you:
private static final TimeZone UTC = TimeZone.getTimeZone("Etc/UTC")
public static Calendar convertYYYYMMDDtoCalendar(String text) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
format.setTimeZone(UTC);
Calendar calendar = new GregorianCalendar(UTC);
calendar.setDate(format.parse(text));
return calendar;
}
(That's assuming you want a time zone of UTC... you'll need to decide that for yourself.)

Check if current time is more than an hour from "last_updated" time

On my Android App, I'd like to only re-import my data if it's been at least X hours since the last import.
I'm storing the last_updated time in my sqlite database in this format: 2012/07/18 00:01:40
How can I get "hours from then" or something like that?
My code thus far:
package com.sltrib.utilities;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class DateHelper
{
public static String now()
{
Calendar currentDate = Calendar.getInstance();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String dateNow = formatter.format(currentDate.getTime());
//System.out.println("Now the date is :=> " + dateNow);
return dateNow;
}
public static int hoursAgo(String datetime)
{
//return the number of hours it's been since the given time
//int hours = ??
//return hours;
}
}
You're going to want to do math between two Calendars or Dates.
Note: Aspects of Date are deprecated, see below for Calendar!
Here's an example using Date:
public static int hoursAgo(String datetime) {
Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH).parse(datetime); // Parse into Date object
Date now = Calendar.getInstance().getTime(); // Get time now
long differenceInMillis = now.getTime() - date.getTime();
long differenceInHours = (differenceInMillis) / 1000L / 60L / 60L; // Divide by millis/sec, secs/min, mins/hr
return (int)differenceInHours;
}
There are some try/catch blocks involved here (which you should probably handle with throws), but this is the basic idea.
Edit: Since parts of Date are deprecated, here is the same method using Calendar:
public static int hoursAgo(String datetime) {
Calendar date = Calendar.getInstance();
date.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH).parse(datetime)); // Parse into Date object
Calendar now = Calendar.getInstance(); // Get time now
long differenceInMillis = now.getTimeInMillis() - date.getTimeInMillis();
long differenceInHours = (differenceInMillis) / 1000L / 60L / 60L; // Divide by millis/sec, secs/min, mins/hr
return (int)differenceInHours;
}
You can also do it directly on the query. Look this question, there is examples of how to calculate difference between two dates:
SQLite: express the difference as days, hours, minutes between two given dates

Categories

Resources