How can I normalize timezones when comparing two Java Calendars? - java

I'm furious with the 7th of November.
I've written a method to calculate the number of days between two Java Date objects (before it gets mentioned, JodaTime is not an option) -- the method works the majority of the time, but when the start xor end date occurs during daylight savings time, the output is off by a day.
Do Calendars have some way of overriding the timezone of dates? I don't care what timezone the dates are actually in, but they need to be the same one!
Code below:
public int getDayRange() {
//startDate = "Sat Nov 06 00:00:00 EDT 2010";
//endDate = "Sun Nov 07 23:59:59 EST 2010";
TimeZone tz = TimeZone.getTimeZone("UTC");
GregorianCalendar cal1 = new GregorianCalendar(tz);
GregorianCalendar cal2 = new GregorianCalendar(tz);
cal1.setTime(startDate);
cal2.setTime(endDate);
long ms1 = cal1.getTime().getTime();
long ms2 = cal2.getTime().getTime();
long difMs = ms2-ms1;
long msPerDay = 1000*60*60*24;
double days = difMs / msPerDay;
return (int) Math.floor(days)+1;
//returns 3(!!!) days (wrong)
}

Never try to count days using 1000*60*60*24 as a day. It's just plain wrong. If you really need to implement the calculation yourself for some reason, use the YEAR and DAY_OF_YEAR fields on the Calendar to count differences in actual days.
You cannot simply "override" the timezone of a java.util.Date because it has no timezone information associated with it. You need to know what timezone it was intended to be interpreted in, and use that timezone when converting it to a human representation. Arbitrarily using UTC to interpret it will, as you have discovered, not deliver consistent results!
public int getDayRange() {
//startDate = "Sat Nov 06 00:00:00 EDT 2010";
//endDate = "Sun Nov 07 23:59:59 EST 2010";
TimeZone tz1 = TimeZone.getTimeZone("EDT");
TimeZone tz2 = TimeZone.getTimeZone("EST");
GregorianCalendar cal1 = new GregorianCalendar(tz1);
GregorianCalendar cal2 = new GregorianCalendar(tz2);
cal1.setTime(startDate);
cal2.setTime(endDate);
if (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) {
return cal2.get(Calendar.DAY_OF_YEAR) - cal1.get(Calendar.DAY_OF_YEAR) + 1;
} else {
//this gets complicated, but you can see what to do, plenty of examples online
}
}

Your system should store all timestamps in UTC!
If not you will never get that to work!
Then the difference calculation is simple.
If you need to display localTime too, you have to store the TimeZone together with the date as a pair: Example: (long timeUtc, int timezoneOffSetToUtc).
The timezoneOffSetToUtc is the offset valid at the creation date of the pair.
The TimeZone should only be instantiated from String like ("Austria / Vienna").

Related

Is the TimeUnit class broken?

I noticed a strange behaviour of the TimeUnit class, so I created this minimal example to reproduce it.
long differenceInDays;
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
c1.setTimeInMillis(1466062306000l); // Thu Jun 16 2016 09:31:46 GMT+0200
c2.setTimeInMillis(1466028000000l); // Thu Jun 16 2016 00:00:00 GMT+0200
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // obviously zero
c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // why zero and not one?
c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // suddenly a 1, but not a 2 like expected
It is obvious that the first time the difference is calculated it is 0, because not a whole day lies between the dates.
But the second time a whole day is added, so how can the difference be still 0?
Output:
001
I don't think this problem is daylight saving time or leap year related because I only do calculations within the same year, even month.
Here is a date to milliseconds calculator for you to check.
You can see better what's going on here with simple math:
c1 = 1466062306000
c2 = 1466028000000
d = 86400000 // one day
c2 - c1 = -34306000 // negative, but less than one day in magnitude
c2 - c1 + d = 52094000 // less than one day
c2 - c1 + d + d = 138494000 // more than one day, less than two days
The correct way to handle this, assuming you're using Java 8, is as follows:
// Decide what time zone you want to work in
ZoneId tz = ZoneId.of("Europe/Berlin");
// If you wanted the local time zone of the system,
// Use this instead:
// ZoneId tz = ZoneId.systemDefault();
// Get instants from the timestamps
Instant i1 = Instant.ofEpochMilli(1466062306000l);
Instant i2 = Instant.ofEpochMilli(1466028000000l);
// Get the calendar date in the specified time zone for each value
LocalDate d1 = i1.atZone(tz).toLocalDate();
LocalDate d2 = i2.atZone(tz).toLocalDate();
// Get the difference in days
long daysBetween = ChronoUnit.DAYS.between(d2, d1);
If your inputs are truly Calendar objects instead of timestamps, I'd suggest Calendar.toInstant() as described in the Legacy Date-Time Code guidance.
If you're using Java 7 or earlier, you will find similar capabilities from the Joda Time library.
if you really don't want to use any of these, and still do things the old (hard) way, then see this example.

How to remove Hour, Minute, and Second from Epoch timestamp

I need to write a function that accepts a java.util.Date and removes the hours, minutes, and milliseconds from it USING JUST MATH (no Date formatters, no Calendar objects, etc.):
private Date getJustDateFrom(Date d) {
//remove hours, minutes, and seconds, then return the date
}
The purpose of this method is to get the date from a millisecond value, without the time.
Here's what I have so far:
private Date getJustDateFrom(Date d) {
long milliseconds = d.getTime();
return new Date(milliseconds - (milliseconds%(1000*60*60)));
}
The problem is, this only removes minutes and seconds. I don't know how to remove hours.
If I do milliseconds - (milliseconds%(1000*60*60*23)), then it goes back to 23:00 hrs on the previous day.
EDIT:
Here's an alternative solution:
public static Date getJustDateFrom(Date d) {
Calendar c = Calendar.getInstance();
c.setTime(d);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
return c.getTime();
}
Will this solution be affected by time zone differences between the client/server sides of my app?
There are 24 hours in a day. Use milliseconds%(1000*60*60*24).
Simply not possible by your definition.
A millisecond timestamp represents milliseconds elapsed from a fixed point in time (1970-01-01 00:00:00.000 UTC, if I remember correctly). This timestamp can not be converted into a date + time without specifying the timezone to convert to.
So you can only round the timestamp to full days in respect to a specific timezone, not in general. So any fiddling with Date.getTime() and not taking into account any timezone is guaranteed to work in only one time zone - the one you hardcoded for.
Do yourself a favor and use a Calendar.
You can make use of apache's commons lang DateUtils helper utility class.
For example, if you had the datetime of 28 Mar 2002
13:45:01.231, if you passed with Calendar.HOUR, it would return 28 Mar
2002 13:00:00.000. If this was passed with Calendar.MONTH, it would
return 1 Mar 2002 0:00:00.000.
Date newDate = DateUtils.truncate(new Date(1408338000000L), Calendar.DAY_OF_MONTH);
You can download commons lang jar at http://commons.apache.org/proper/commons-lang/
import java.sql.Date;
long dateInEpoch = 1_592_283_050_000L;
ZoneId defaultZoneId = ZoneId.systemDefault();
long currentDate = Date
.from(new Date(dateInEpoch)
.toLocalDate()
.atStartOfDay(defaultZoneId)
.toInstant())
.getTime();
input : 1592283050000
output: 1592245800000

Long To XMLGregorianCalendar and back to Long

I am trying to convert from millisecond time stamp to XMLGregorianCalendar and back, but I seem to be getting wrong results. Am I doing something wrong? It seems I am gaining days.
// Time stamp 01-Jan-0001 00:00:00.000
Long ts = -62135740800000L;
System.out.println(ts);
System.out.println(new Date(ts)); // Sat Jan 01 00:00:00 PST 1 .. Cool!
// to Gregorian Calendar
GregorianCalendar gc = new GregorianCalendar();
gc.setTimeInMillis(ts);
// to XML Gregorian Calendar
XMLGregorianCalendar xc = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
// back to GC
GregorianCalendar gc2 = xc.toGregorianCalendar();
// to Timestamp
Long newTs = gc2.getTimeInMillis();
System.out.println(newTs); // -62135568000000 .. uh?
System.out.println(new Date(newTs)); // Mon Jan 03 00:00:00 PST 1 .. where did the extra days come from?
Interesting - it works fine for values down to (about) -10000000000000L (and positive values) but larger negative values become inconsistent.
If you print out gc, xc, and gc2, you can see where the problem arises (the conversion from XMLGregorianCalendar to GregorianCalendar
gc: java.util.GregorianCalendar[time=-62135740800000 ... DAY_OF_WEEK=7
xc: 0001-01-01T08:00:00.000Z
gc2: java.util.GregorianCalendar[time=? ... DAY_OF_WEEK=5
If you print out the fields of xc, you get 1,1,1.
System.out.println(xc.getYear());
System.out.println(xc.getMonth());
System.out.println(xc.getDay());
For gc2, you get 1,0,1 (which matches xc, because months are zero-based in GregorianCalendar)
System.out.println(gc2.get(gc2.YEAR));
System.out.println(gc2.get(gc2.MONTH));
System.out.println(gc2.get(gc2.DAY_OF_MONTH));
However, adding these 3 println calls changes the output from printing out gc2! - the time=? output from gc2 changes to time=-62135568000000 - so some calculation has been triggered by querying the GregorianCalendar object; the areFieldsSet property also changes from false to true.
The timezones of the two GregorianCalendars are different, but this does not account for the error, which persists even if you set explicit TimeZone and Locale.
I believe here is the problem. Per documentation, toGregorianCalendar() relies on the GregorianCalendar corresponding defaults for conversion when there is any field missing.
If you try:
Date date = new Date();
long ts = date.getTime(); //in place of your input
and run your code, you should find, both to and from conversion working fine.
If you want your toGregorianCalendar() with custom provide inputs as in your example, please use toGregorianCalendar(TimeZone,Locale,Defaults) and supply the updated defaults to be used in conversion.

Java Convert unixtime to date , giving wrong results

Im trying to convert unixtime to date but the results im getting are wrong :
for example i have this unixtime : 1354312800 accurding to this site :
enter link description here
the result is :
Fri, 30 Nov 2012 22:00:00 GMT
but when i do :
long timestamp = 1354312800;
java.util.Date time=new java.util.Date((long)timestamp*1000);
int d = time.getDay();
int m = time.getMonth();
im getting :
d= 6 << this is wrong should be 30.
and m - 11
You have mistaken getDay()for getDate().
getDay Javadoc:
Returns the day of the week represented by this date. The returned value (0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday) represents the day of the week that contains or begins with the instant in time represented by this Date object, as interpreted in the local time zone.
So just use getDate() instead of getDay()
For more info, check the javadoc: http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Date.html
If your time still differs from the Unix time you tried, you are probably living not in the GMT Timezone, so you need to find out the date for the corresponding timezone:
SimpleDateFormat df = new SimpleDateFormat();
df.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println(df.format(time));
This should give you the expected output, even though your local tiemzone differs from GMT
The method Date.getDay() gives the day of the week (0 = Sunday, ..., 6 = Saturday).
Change it to Date.getDate() and you will get 30 as the result.
Some side-notes:
The Date class is pretty much deprecated. Use Calendar instead, or even better, the Joda time library.
Your conversion is sort of funny. (long)timestamp*1000 converts timestamp to a long value (which is already a long value) and then automatically widens 1000 to a long value to carry out the multiplication.
I would skip the conversion, (long) , altogether, and if you want to explicitly say that the factors are long values, use 1000L instead, which is a long-literal.

Java Date determine if DAY is greater

I have time stamps as string format Sun Jul 10 17:47:55 EDT 2011
I need to determine if the current DAY is greater than the stored day.
I will get the current day with
Date currentDate = new Date();
and I will parse my string into a Date object with SimpleDateFormat dateParser = new SimpleDateParser("E MMM d HH:mm:ss z y");
but using the "currentDate.after()" function, or the currentDate.compare() function will only tell me if ANY date is greater or less than, which includes by the hour,minute or second.
So My next hunch would be to convert the date into the day of the year, and compare the new integers, if integer1>integer2, then.. but how would I do that?
I also considered breaking the string up to a substring consisting of only the first half of the stored string date. Sun Jul 10 to Sun Jul 10 but the problem here is that the day value is sometimes 1 digit and othertimes 2 digits.
Also I think the Calendar abstract class is the best way to go about this, but I am unsure, and currently in a fog about how to convert the Date objects into the Calendar object for comparison!
Insight appreciated
This may be helpful in your case (as quick way):
Locale.setDefault(Locale.UK);
String storedDateString = "Sun Jul 10 17:47:55 EDT 2011";
SimpleDateFormat dateParser = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy");
Date storedDate = dateParser.parse(storedDateString);
GregorianCalendar storedCal = new GregorianCalendar();
storedCal.setTime(storedDate);
GregorianCalendar currentCal = new GregorianCalendar();
int storedDayOfYear = storedCal.get(Calendar.DAY_OF_YEAR);
int currentDayofYear = currentCal.get(Calendar.DAY_OF_YEAR);
//int storedDayYear = storedCal.get(Calendar.YEAR);
//int currentDayYear = storedCal.get(Calendar.YEAR);
System.out.println(storedDayOfYear);
System.out.println(currentDayofYear);
//System.out.println(storedDayYear);
//System.out.println(currentDayYear);
Result:
191
192
After that it's trivial to compare int values.
If you want to convert a Date object into a Calendar objet, use
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
You can get the day value of the Calendar object with
cal.get(Calendar.DAY_OF_MONTH);
or
calendar.get(Calendar.DAY_OF_YEAR);
however, note that when comparing dates of different months (first example) or years (second example) this wont work. You should better set the milliseconds, seconds, minutes and hours to 0 on each Calendar object and the compare the objects:
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
c1.setTime(date1);
c2.setTime(date2);
c1.set(Calendar.MILLISECOND, 0);
c1.set(Calendar.SECOND, 0);
c1.set(Calendar.MINUTE, 0);
c1.set(Calendar.HOUR, 0);
c2.set(Calendar.MILLISECOND, 0);
c2.set(Calendar.SECOND, 0);
c2.set(Calendar.MINUTE, 0);
c2.set(Calendar.HOUR, 0);
c1.compareTo(c2);
Calendar cal = Calendar.getInstance(); // use the getInstance(TimeZone zone) overload
// if you want some other timezone than the
// default one
cal.setTime(yourDate);
cal.get(Calendar.DAY_OF_MONTH);
First of all, convert the Date object to String using toString(). Once this is done, split the String using split(" "). This will return an String[]. Since both the formats are same, hence in both the arrays, the third element will contain the date and second element will be month.
If the month is same, then you only have to see which date is bigger than the other (You can also check the years).
tl;dr
ZonedDateTime.parse(
"Sun Jul 10 17:47:55 EDT 2011" ,
DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss z uuuu" , Locale.US )
).getDayOfYear()
191
Day-of-year
Apparently you want to compare the day-of-year of two dates, how far into the year each date goes regardless of the year and regardless of the time-of-day.
This day-of-year idea is often mislabeled as Julian dates, but is properly known as an Ordinal Date. And even that includes a year, such as 2017-186. But apparently you want simply the day with in the year, a number from 1 to 365 or 366.
Using java.time
The modern approach uses the java.time classes that supplant the troublesome old date-time classes you are using.
First parse your string as a ZonedDateTime.
String input = "Sun Jul 10 17:47:55 EDT 2011" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss z uuuu" , Locale.US ) ;
ZonedDateTime zdt = ZonedDateTime.parse( input , f ) ;
zdt.toString(): 2011-07-10T17:47:55-04:00[America/New_York]
By the way, never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!). Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland.
Lastly, get the day-of-year integer number you seek.
int dayOfYear = zdt.getDayOfYear() ;
dayOfYear: 191
See this code run live at IdeOne.com.

Categories