Java Date problems, finding the date X days ago - java

Date nowdate = new Date();
long nowms = nowdate.getTime();
long differencems = numdaysback * 24 * 60 * 60 * 1000;
long thenms = nowms - differencems;
Date thendate = new Date(thenms);
If numdaysback is 365, then I would suppose that thendate would be one year ago. but it's not... it's about three weeks ago?!?
NUMDAYSBACK: 365
NOWDATE: Wed Jun 22 20:31:58 SGT 2011
NOWMS: 1308745918625
DIFFERENCEMS: 1471228928
THENMS: 1307274689697
THENDATE: Sun Jun 05 19:51:29 SGT 2011

How about:
Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -1);
Date thendate = cal.getTime();
Returns the same time of day regardless of DST or leap years, is shorter and clearer...
Generally Calendar is the way to go in such cases (unless you use a 3rd party library like Joda Time). You can use it for all kinds of calculations: add N days/hours/months/seconds, truncate time to a whole hour etc. - stuff that would be too much pain with long only.
Regarding your original question, it seems to be a victim of integer overflow. It works if the multiplication explicitly uses long:
long differencems = 365 * 24 * 60 * 60 * 1000L;

If you are using Java 8 and up, you can use the newer java.time library to do this a bit more cleanly.
Date xDaysAgo = Date.from( Instant.now().minus( Duration.ofDays( x ) ) );

Just try this:
long differencems = numdaysback * 24L * 60 * 60 * 1000;
With the new code you will not loose the digits due to integer multiplication.
Since we have marked the literal 24 as long, the multiplication will be done by auto converting the first operand numdaysback into long. The rest of the multiplication will also be done on long operands.

This line:
long differencems = numdaysback * 24 * 60 * 60 * 1000;
the RHS should be: 31536000000. You have something much less, the reason being the RHS is being evaluated as an int (as all the quantities are ints), and you are exceeding MAX_INT. To correct to this:
long differencems = numdaysback * 24 * 60 * 60 * 1000l;
Note the "l" which makes 1000 be a long - now the RHS will be evaluated as a long.

The Date class is (informally) deprecated. The API has so many faults, that it is really difficult to get Dates/Times right with it. The easiest example is something like your code for differencems. It fails, if the time inbetween contains a daylight savings switch (if you don't use UT) and will always fail to take care of leap seconds.
If your application depends on correct dates, you might want to use Joda Time.

Related

Java Difference between current date and past date in Years, Months, Days, Hours, Minutes, Seconds [duplicate]

This question already has answers here:
integer giving negative values in java in multiplication using positive numbers [duplicate]
(4 answers)
Closed 3 years ago.
I know this has been asked on here many times previously, but I'm haven't been able to find anything specific to my case. I'm trying to find the difference between the current datetime and a previous datetime, each with the format yyyy-MM-dd HH:mm:ss.s. Based on the answer given here, I've come up with the following code:
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.s");
String earliestRunTime = "2017-12-16 01:30:08.0";
Date currentDate = new Date();
log.info("Current Date: {}", format.format(currentDate));
try {
Date earliestDate = format.parse(earliestRunTime);
long diff = currentDate.getTime() - earliestDate.getTime();
long diffSeconds = diff / 1000 % 60;
long diffMinutes = diff / (60 * 1000) % 60;
long diffHours = diff / (60 * 60 * 1000) % 24;
long diffDays = diff / (24 * 60 * 60 * 1000) % 30;
long diffMonths = diff / (30 * 24 * 60 * 60 * 1000) % 12;
long diffYears = diff / (12 * 30 * 24 * 60 * 60 * 1000);
return String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
diffYears, diffMonths, diffDays, diffHours, diffMinutes, diffSeconds);
}
catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
When I run the code, the JSON returns the following result:
lifetime: "41 years, -1 months, 14 days, 9 hours, 42 minutes, 37 seconds"
I have two questions here:
Where am I going wrong in my calculations 41 years and a negative number?
Is there a better way for me to do this? My current setup does not consider leap years or a 365 day year, and I need to take these into account.
Where am I going wrong in my calculations 41 years and a negative number?
Because the denominator will overflow. You need to use Long:
long diffMonths = diff / (30 * 24 * 60 * 60 * 1000L) % 12; //Notice the L at the end
long diffYears = diff / (12 * 30 * 24 * 60 * 60 * 1000L); //Notice the L at the end
Also note that 12 * 30 is a really bad approximation of the number of days in a year.
Is there a better way for me to do this?
Yes. Use Duration api from Java 8. https://www.mkyong.com/java8/java-8-period-and-duration-examples/
It's hard to give precise answer, because the question is a bit vague. For example - If one of the year was a leap year and you were comparing dates 2020/03/28 and 2021/03/28, what should be the result? 1 year or 1 years, 1 days? (2020 is a leap year so after 03/28, there's also 03/29)
Where am I going wrong in my calculations 41 years and a negative number?
Apart from using the notoriously troublesome and long outdated SimpleDateFormat class and the just as outdated Date there are the following bugs in your code:
You are parsing 08.0 as 8 seconds 0 seconds. On my JDK-11 SimpleDateFormat opts for the 0 seconds and discards the 8 seconds that I think are correct. SimpleDateFormat cannot parse one decimal on the seconds (only exactly three decimals), so the solution to this bug is discarding SimpleDateFormat altogether.
As others have said you have an int overflow in your multiplications. For example, 30 * 24 * 60 * 60 * 1000 should give 2 592 000 000, but since an int cannot hold this number, you get -1 702 967 296 instead. Since this is a negative number, the following division gives you a negative number of months.
As Solomon Slow pointed out in a comment, a month may be 28, 29, 30 or 31 days. When setting all months to 30 days you risk incorrect numbers of days and months and in the end also years. When I ran your code today, the correct answer would have been 1 year, 4 months, 13 days, but I got 19 days instead, 6 days too much.
You are not taking summer time (DST) and other time anomalies into account. These may cause a day to be for example 23 or 25 hours, giving an error.
Or to sum up: Your error was that you tried to do the calculation “by hand”. Date and time math is too complex and error-prone to do this. You should always leave it to well-proven library classes instead.
Is there a better way for me to do this? My current setup does not consider leap years or a 365 day year, and I need to take these into
account.
Yes, there is a much better way. The best way may be to use the PeriodDuration class from the ThreeTen Extra project, see the link below. I am not going to install that library in my computer right now, so I will just show the good and modern solution using built-in classes:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S");
LocalDateTime currentDateTime = LocalDateTime.now(ZoneId.of("Australia/Sydney"));
String earliestRunTime = "2017-12-16 01:30:08.0";
LocalDateTime earliestDateTime = LocalDateTime.parse(earliestRunTime, dtf);
// We want to find a period (years, months, days) and a duration (hours, minutes, seconds).
// To do that we cut at the greatest possible whole number of days
// and then measure the period before the cut and the duration after it.
LocalDateTime cut = earliestDateTime.plusDays(
ChronoUnit.DAYS.between(earliestDateTime, currentDateTime));
Period p = Period.between(earliestDateTime.toLocalDate(), cut.toLocalDate());
Duration d = Duration.between(cut, currentDateTime);
String result = String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
p.getYears(), p.getMonths(), p.getDays(),
d.toHours(), d.toMinutesPart(), d.toSecondsPart());
System.out.println(result);
When I ran the code just now I got:
1 years, 4 months, 13 days, 19 hours, 26 minutes, 7 seconds
In java.time, the modern Java date and time API, a Period is an amount of years, months and days, and a Duration is an amount of hours, minutes, seconds and fraction of second (down to nanoseconds). Since you wanted both, I am using both classes.
The toXxxPart methods of Duration I am using were introduced in Java 9. If you are using Java 8 (or the ThreeTen Backport) printing the minutes and seconds is a little bit more complicated. Search for java format duration or similar to learn how.
I am still not taking summer time into account. To do that we would need to know the time zone of the earliest run time string and then use ZonedDateTime instead of LocalDateTime. The code would otherwise be very similar.
Links
ThreeTen Extra
Documentation of PeriodDuration
Oracle Tutorial: Date Time explaining how to use java.time
Using the same approach you did, you need to explicitly identify the denominator as long values. Currently, it assumes them to be integers, which causes a numeric overflow - meaning the value computed is too large for a integer. This would explain why you get negative/arbitrary values. Fix is simple:
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.s");
String earliestRunTime = "2017-12-16 01:30:08.0";
Date currentDate = new Date();
log.info("Current Date: {}" + format.format(currentDate));
try {
Date earliestDate = format.parse(earliestRunTime);
long diff = currentDate.getTime() - earliestDate.getTime();
long diffSeconds = diff / 1000L % 60L;
long diffMinutes = diff / (60L * 1000L) % 60L;
long diffHours = diff / (60L * 60L * 1000L) % 24L;
long diffDays = diff / (24L * 60L * 60L * 1000L) % 30L;
long diffMonths = diff / (30L * 24L * 60L * 60L * 1000L) % 12L;
long diffYears = diff / (12L * 30L * 24L * 60L * 60L * 1000L);
return String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
diffYears, diffMonths, diffDays, diffHours, diffMinutes, diffSeconds);
}
catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}

Getting a correct date by adding substracting a number of milliseconds

Today is 18/12/2013.
I was testing this program where I wanted get another date by adding/substracting a number of milliseconds from System.currentMillis(). It works well when I shift the date in decembre but it doesn't work correctly for some similar values. For example the following code gives me 2013 12 20 as the result! I wonder, no I still wonder how it could be possible! or I'm making a mistake?
public class CreateDirFiles {
public static final String PLOG_DIR = "ProductLog";
private SimpleDateFormat dateFormatter = new SimpleDateFormat();
public CreateDirFiles() {
}
public void createDayDir(Date date){
dateFormatter.applyPattern("YYYY");
String year = dateFormatter.format(date);
dateFormatter.applyPattern("MM");
String month = dateFormatter.format(date);
dateFormatter.applyPattern("dd");
String day = dateFormatter.format(date);
System.out.printf("%s %s %s\n", year, month, day);
}
public static void main(String... args){
CreateDirFiles dfc = new CreateDirFiles();
dfc.createDayDir(new Date(System.currentTimeMillis() -
((long)( 48 * 24 * 3600 * 1000)) ));
}
}
This is the problem:
((long)( 48 * 24 * 3600 * 1000))
That's doing all the arithmetic in 32 bits, and then converting the (now truncated, as the result is too large for an int) result to a long. You want:
48L * 24 * 3600 * 1000
where the L suffix means that it'll use a long for the value 48.
However, you really don't want to do this at all - you want to use Joda Time which is a much nicer API for date/time work. You really don't want to have to mess around with the low level stuff at all.
LocalDate date = ...;
LocalDate twoDaysLater = date.minusDays(48);
If you really want to stick with the built-in API, then use Calendar. At the very least use the TimeUnit enum, which will allow:
long millisFor48Days = TimeUnit.DAYS.toMillis(48);
You also need to consider the time zone - while "today" may be the 18th of December for you, it isn't elsewhere in the world.

4 hours in currentTimeMillis()

I have simple question, I have the following function and there is argument on it that called cacheTime, How can I set it to 4 hours, should I set it to 4 * 3600000?
public static File getCache(String name, Context c, int cacheTime)
{
if (cacheTime <= 0)
return null;
File cache = new File(c.getCacheDir(), name);
long now = System.currentTimeMillis();
if (cache.exists() && (now - cache.lastModified() < cacheTime))
return cache;
return null;
}
miliseconds are 1/1000 of a second. So 4 hours would be 4 * 60 * 60 * 1000 = 14,400,000
For cache invalidation this is probably fine. That said, date math is often dangerous. When dealing with larger units of time than milliseconds one can easily get tripped up during daylight savings transitions, leap seconds and all the other stuff that Calendar is meant to take care of. In some cases that rare imprecision is acceptable, and in others it's not. Be careful when doing date math.
For determining human consumable times in larger units of time such as +1 days, use Calendar.roll().
Learn to use the handy TimeUnit enum so you can do things like so:
TimeUnit.Hours.toMillis(4)
And not rely on napkin math and magic numbers all over your code.
// 4 hours * 60 (min/hour) * 60 (sec/min) * 1000 (msec/sec)
getCache(name, c, 4 * 3600 * 1000);
4 * 1000 * 3600
There are 1000 milliseconds in a second and 3600 seconds in an hour.

How do I add time to a timestamp?

I need to add 14 minutes and 59 seconds to an unknown time in an array. How do I do this? This is what I have so far:
Date duration = df.parse("0000-00-00 00:14:59");
arrayOpportunity[2] = arrayOpportunity[2] + duration;
The time is not being changed. Thanks!
I have done my research. I cant paste the entire code I have. But mainly I didnt want to make you read it all. Just looking for a simple answer of how to add two timestamps.
If you are talking about a java.sql.Timestamp, it has a method called setTime. java.util.Date has a setTime method as well for that sort of thing.
You could something like this:
static final Long duration = ((14 * 60) + 59) * 1000;
oldTimestamp.setTime(oldTimestamp.getTime() + duration);
If you want to add time in millis then you can just add
(((14 * 60) + 59) * 1000) <-- Mili second value of 14 m and 59 sec
If you just want to add times, I suggest using Joda Time.
The class LocalTime lets you add durations like this:
LocalTime timeSum = time.plusMinutes(14).plusSeconds(59);
Just add the appropriate number of milliseconds using #getTime() and #setTime():
timeStamp.setTime(timeStamp.getTime() + (((14 * 60) + 59)* 1000));
arrayOpportunity[2] = arrayOpportunity[2] + 14*60*1000 + 59*1000;
The Date object you have may work, but it doesn't really represent 14 minutes and 59 seconds, it just represents a particular time in calendar (eg. 14 minutes 59 after the epoch start which is 1st January 1970 00:14:59).

How do I increment a java.sql.Timestamp by 14 days?

I have an app that takes a Timestamp as a boundary for the start date and end date of a sql selection, I want to populate a hashmap with weeks this year since the first monday of the year as the values and the week number as the keys. I'm finding it really hard to work with timestamps and I don't feel very good about adding 86,400,000 seconds to it to increment the day, as this doesn't account for the leap days, hours, seconds.
I plan on adding 13 days 23 hours, 59 minutes and 59 seconds to it so that I can lookup the start date in the map by the week as the key, then use the start date to get the end date.
So I'm looking to try to get something like this:
Week startDate endDate
1 2011-01-03 00:00:00 2011-01-16 23:59:59
2 2011-01-17 00:00:00 2011-01-30 23:59:59
With the first two columns in the Map and the last one being calculated after looking it up. How do I safely increment a java.sql.Timestamp?
java.sql.Timestamp ts = ...
Calendar cal = Calendar.getInstance();
cal.setTime(ts);
cal.add(Calendar.DAY_OF_WEEK, 14);
ts.setTime(cal.getTime().getTime()); // or
ts = new Timestamp(cal.getTime().getTime());
This will correctly cater for daylight-time transitions in your default Timezone. You can tell the Calendar class to use a different Timezone if need be.
It worth noting that 14 days is not always 14 * 24 * 3600 seconds. When you have daylight savings, this can be an hour shorter or longer. Historically it can be much more complex than that.
Instead I would suggest using JodaTime or the Calendar to perform the time zone dependant calculation.
Java 8
Timestamp old;
ZonedDateTime zonedDateTime = old.toInstant().atZone(ZoneId.of("UTC"));
Timestamp new = Timestamp.from(zonedDateTime.plus(14, ChronoUnit.DAYS).toInstant());
private Long dayToMiliseconds(int days){
Long result = Long.valueOf(days * 24 * 60 * 60 * 1000);
return result;
}
public Timestamp addDays(int days, Timestamp t1) throws Exception{
if(days < 0){
throw new Exception("Day in wrong format.");
}
Long miliseconds = dayToMiliseconds(days);
return new Timestamp(t1.getTime() + miliseconds);
}
Timestamp my14DaysAfter = Timestamp.valueOf(myTimestamp.toLocalDateTime().plusDays(14));

Categories