I got strange java casting problem today coming from such code
new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 31)
This is supposed to give date 31 days before now, but returns date 16 days after. It obviously happens because
1000 * 60 * 60 * 24 * 31
is evaluated as Integer and overflows.
new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 31)
works as expected
I think java should cast whole expression to Long because first operand is Long System.currentTimeMillis() but it's not happening here for some reason I don't understand. Is there some exception about hardcoded constants to be int ?
It’s all been said, but I thought it deserved to go into an answer. Use the ZonedDateTime class with ZoneId.
ZonedDateTime aMonthAgo = ZonedDateTime.now(ZoneId.of("Indian/Comoro")).minusMonths(1);
Output on my computer just now (April 11):
2018-03-11T19:57:47.517032+03:00[Indian/Comoro]
I subtract a month, so that means 28, 29, 30 or 31 days depending on the month I’m in and the number of days in the previous month. If you want 31 days unconditionally, you can have that, of course:
ZonedDateTime thirtyoneDaysAgo
= ZonedDateTime.now(ZoneId.of("Indian/Comoro")).minusDays(31);
Since there were 31 days in March, the result is the same in this case. It won’t always be.
I am using and recommending java.time, the modern Java date and time API. It’s so much nicer to work with and much less error-prone than the outdated Date class.
What went wrong in your code?
It’s about operator precedence. 1000 * 60 * 60 * 24 * 31 consists of int values. Yes, integer literals have type int unless they have the L suffix. Because multiplication is carried out before subtraction (as you had already expected), the result is an int too, but it overflows because the result would be greater than the maximum number that an int can hold. Unfortunately Java doesn’t inform you of the overflow, it just gives you a wrong result, here -1616567296, about -19 days. When subtracting these, you get a date and time about 19 days into the future.
As a habit, use parentheses, the L suffix, and underscore-grouping for readability.
( System.currentTimeMillis() - ( 1_000L * 60L * 60L * 24L * 31L ) )
If you wanted to be made aware of overflow, you may use Math.multiplyExact() for your multiplications (since Java 8). Fortunately, the modern library classes save you completely from multiplying. And signal any overflow.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Math.multiplyExact() documentation
Related
I came across following formula in which localTimeDay is being retrieved from TimeStamp and Timezone:
long localTimeDay = (timeStamp + timeZone) / (24 * 60 * 60 * 1000) * (24 * 60 * 60 * 1000);
I am not able to understand what is the meaning of Local Time Day and how this formula "magically" converts based on TS and TZ.
timestamp is a count of milliseconds since the epoch of Jan 1, 1970 at 00:00 UTC.
timeZone is the offset in milliseconds from UTC. It may be positive, negative or zero. It must have its sign reversed, though, for the formula to work, as far as I can see: an offset of +01:00 should be given as minus 3 600 000 milliseconds. When I add the offset, I get a different point in time where the date and time of day in UTC is the same as the original date and time of day at that UTC offset.
Dividing by 24 * 60 * 60 * 1000 converts from milliseconds to days since the epoch. Any fraction of a day, that is, any time of the day, is discarded.
Multiplying by 24 * 60 * 60 * 1000 converts back from days to milliseconds. Since the time of day was discarded, we now have the time at 00:00 UTC on that day.
So it’s a way of converting from a point in time at some UTC offset to the date alone represented in UTC.
It’s not code that you would want to have in your program. You should leave such conversions to proven library methods. It might however be code that could be found inside such a tested and proven library.
Edit: Why do I believe that the sign of the offset has been reversed? Couldn’t it be the opposite conversion, from UTC to local? The variable name localTimeDay seems to suggest this? The division and multiplication only works in UTC. Since the epoch is at 00:00 UTC, the formula necessarily gives you the start of a day in UTC, it cannot give you the start of a day at any non-zero offset. Therefore I figure that the conversion must be from local to UTC.
How to do in your code
Here’s a nice way of doing the same conversion using java.time, the modern Java date and time API
// The point in time given in UTC
Instant time = Instant.parse("2020-04-23T05:16:45Z");
// The UTC offset, +02:00
ZoneOffset offset = ZoneOffset.ofHours(2);
ZonedDateTime startOfDayInUtc = time.atOffset(offset)
.toLocalDate()
.atStartOfDay(ZoneOffset.UTC);
System.out.println(startOfDayInUtc);
long epochMillis = startOfDayInUtc.toInstant().toEpochMilli();
System.out.println(epochMillis);
Output:
2020-04-23T00:00Z
1587600000000
We can check that it gives the same result as your code line:
long timeStamp = time.toEpochMilli();
// Reverse sign of offset
long timeZone = -Duration.ofSeconds(offset.getTotalSeconds()).toMillis();
long localTimeDay = (timeStamp + timeZone) / (24 * 60 * 60 * 1000) * (24 * 60 * 60 * 1000);
System.out.println(localTimeDay);
System.out.println("Agree? " + (localTimeDay == epochMillis));
1587600000000
Agree? true
Edit: you asked in a comment:
I still have this doubt: There is fixed number of millis that have
elapsed since Jan 1 1970, so every country should get same number.
Does localTimeDate mean "date for my country" or to the country where
this data came from? For example: Country-A , Country-B; they will see
same millis since Epoch. Suppose data processing is happening in
Country-A and origin of data is Country-B. So when we say
"localTimeDate" , does it pertain to Country-A or Country-B.
It will work for both countries. It all depends on the timeZone value that enters into the formula. If that is the UTC offset for country A (sign reversed), the conversion will be from country A time to UTC date. If it’s country B’s offset, the conversion will be from country B time. And you are fully correct, you will get two different dates if it’s not the same date in countries A and B, which could easily be the case.
This question already has answers here:
Why is this not casting to long
(1 answer)
How to subtract X day from a Date object in Java?
(10 answers)
Closed 4 years ago.
I need to subtract several months of time from a java.util.Date object to get a different java.util.Date. I know that there is a simple way to do this in java 8, however this must be run on my school's server that does not support java 8. My current solution looks like this:
int numDaysToSubtract = 60;
Date curDate = new Date();
//subtract numdays * hours/day * mins/hour * secs/min * millisecs/sec
Date newDate = new Date(curDate.getTime() - (numDaysToSubtract * 24 * 3600 * 1000));
curDate is 4/12/2018 and the calculated newDate is 4/2/2018, which is clearly not 60 days before 4/12/2018.
Why isn't this working as expected?
What should I try instead?
You have overflowed the number of milliseconds to subtract, because the product doesn't fit in a 32-bit integer, whose maximum value is about 2.1 billion (10 digits).
60 days worth of milliseconds is 5,184,000,000, over the limit. Because of the overflow, the product is calculated at 889,032,704 milliseconds, or about 10.2 days.
Either cast numDaysToSubtract as a long, or declare it to be long to begin with, to force long calculations, avoiding overflow. For me, doing that results in February 11, 2018, 60 days ago.
Edit:
lets say I have the date 11.11.2016 for what I want the Unix timestamp.
At the moment I just use
final int start = (int) ((System.currentTimeMillis() - 24 * 60 * 60 * 1000 * 5) / 1000L);
for 10.11.2016 it would be
final int start = (int) ((System.currentTimeMillis() - 24 * 60 * 60 * 1000 * 6) / 1000L);
But it looks somehow so basic :)
Is there maybe a better solution? I couldn't find any function in Date nor LocalTime.
Thanks!
It only makes sense to convert a "date" to epoch seconds if you specify a time of day and a time zone that you want the epoch seconds for.
Otherwise, the conversion is undefined, because a "date" is a 24-hour period (usually) in a specific timezone, whereas a unix timestamp is a number of seconds since 1970-1-1 00:00:00 UTC.
LocalDate.of(2016, 5, 10)
// e.g. LocalTime.of(12, 0) or .atStartOfDay()
// - *not* midnight, in general, since this does not always exist.
.atTime(someLocalTime)
// e.g. ZoneId.of("UTC")
.atZone(someTimeZoneId)
.toEpochSecond();
I have a piece of code that is used to calculate the number of days between two Date objects, and in most instances, it works fine.
However, if the date range between the two objects includes the end of March, the result is always 1 less than it should be. e.g March 31 2014 - March 29 2014 = 1, whereas it should be 2.
I understand that this is due to the fact that March has 30 days of 24 hours and 1 day of 23 hours due to DST, which is the cause of the value being 1 less.
However, I am not sure the best way to account for the missing hour.
// This was what I have initially
int numDays = (int) ((dateTo.getTime() - dateFrom.getTime()) / (1000 * 60 * 60 * 24));
// I have tried rounding, since it should have 23 hours left over, but it didn't actually work.
int numDays = (Math.round(dateTo.getTime() - dateFrom.getTime()) / (1000 * 60 * 60 * 24));
Any help/pointers would be greatly appreciated.
I am and have to use Java 7 and I am not able to use Jodatime unfortunately.
Your second example is very close. Your parentheses for Math.round() only surround the subtraction, though, so since that's already an integer (well, a long really), nothing happens, and then you divide. The other problem with your second bit of code is that you are doing integer division which always truncates the part after the decimal point. Try this:
long numDays2 = Math.round((dateTo.getTime() - dateFrom.getTime()) / (1000.0 * 60 * 60 * 24));
(As indicated, I changed the Math.round() parens, and made it floating point division by making the divisor a double.)
As indicated by the comments, though, this is a hack. In particular, it will tell you that there are two days between 6AM March 5 and 8PM March 6. It's probably not really what you want. Try this on for size instead:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal = Calendar.getInstance();
cal.setTime(fmt.parse("2014-03-29"));
long start = cal.getTimeInMillis();
start += cal.getTimeZone().getOffset(start);
cal.setTime(fmt.parse("2014-03-31"));
long end = cal.getTimeInMillis();
end += cal.getTimeZone().getOffset(end);
System.out.println((end - start)/86400000.0);
Is it ugly? Yes. Is it weird? Yes. Does it work? Yes (I think so). Note that I'm providing a double as a result; you can apply any rounding you want to this result.
I have two objects, p4 and p5, that have a Date property. At some points, the constructor works fine:
p4.setClickDate(new Date(System.currentTimeMillis() - 86400000 * 4));
Sets the date to Sun Jul 31 11:01:39 EDT 2011
And in other situations it does not:
p5.setClickDate(new Date(System.currentTimeMillis() - 86400000 * 70));
Sets the date to Fri Jul 15 04:04:26 EDT 2011
By my calculations, this should set the date back 70 days, no?
I can get around this using Calendar, but I'm curious as to why Date behaves this way.
Thanks!
That's caused by an integer overflow. Integers have a maximum value of Integer.MAX_VALUE which is 2147483647. You need to explicitly specify the number to be long by suffixing it with L.
p5.setClickDate(new Date(System.currentTimeMillis() - 86400000L * 70));
You can see it yourself by comparing the results of
System.out.println(86400000 * 70); // 1753032704
System.out.println(86400000L * 70); // 6048000000
See also:
Java Tutorials - Language Basics - Primitive Data Types
the number is too big and you have overflow you should add L at the end to make it long.\8640000l (java numbers are int by default)