There are two parts to this: first compute the start date. Then compute the elapsed time. For the second part, I found some good advice here: Calculate elapsed time in Java / Groovy
However, the first part is a problem for me. Here's how it works:
The user indicates that the time span extends from an hour value (00 to 23). I do not have a starting date/time object of any sort - just an integer hour. From that I need to figure the start date (and then the elapsed time).
If the start hour is greater than the now hour, it was the prior day. In order to get an actual start date from that however, I need to potentially consider month and year boundaries as well as things like leap years and daylight savings time changes. Surely someone has solved a problem like this already. (I believe it can be quite complex.) Is there a proven solution that will let me compute how much time (in seconds) has actually elapsed from the given hour of the day (00 to 24) to now? (The start time will always be assumed to be on the hour.)
Firstly, I'd suggest using the Joda Time API. It's the best date/time API available for Java, in my opinion.
Next you need to work out exactly what to do in various corner cases. In particular, suppose the user enters "1" and you're near a daylight saving transition. It's possible that 1am happened twice (if the the time went 1:58, 1:59, 1:00, 1:01 because of a transition back away from DST) or that it didn't happen at all (if the time went 12:58, 12:59, 2:00 because of a transition forward into DST). You need to work out what to do in each of those situations - and bear in mind that this means knowing the time zone too.
Once you've worked that out, it may not be too hard. With Joda Time you can use withHourOfDay method to get from one time to another having set one component of the time - and likewise there are simple APIs for adding or subtracting a day, if you need to. You can then work out the time between two DateTime values very easily - again, Joda Time provides everything you need.
Here's an example which doesn't try to do anything with DST transitions, but it's a good starting point:
import org.joda.time.*;
public class Test
{
public static void main(String[] args)
{
// Defaults to current time and time zone
DateTime now = new DateTime();
int hour = Integer.parseInt(args[0]);
DateTime then = now
.withHourOfDay(hour)
.withMinuteOfHour(0)
.withSecondOfMinute(0);
if (then.isAfter(now))
{
then = then.minusDays(1);
}
Period period = new Period(then, now, PeriodType.seconds());
System.out.println("Difference in seconds: " + period.getSeconds());
}
}
java.time
The accepted answer has provided a solution using Joda-Time API which was probably the best 3rd party date-time API in Java at that time. In Mar 2014, the modern date-time API was released as part of the Java 8 standard library. Notice the following message on the Home Page of Joda-Time:
Joda-Time is the de facto standard date and time library for Java
prior to Java SE 8. Users are now asked to migrate to java.time
(JSR-310).
Solution using java.time, the modern date-time API:
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
class Main {
public static void main(String[] args) {
// A sample hour indicated by the user
int hour = 16;
// Replace the ZoneId with the applicable one
ZoneId zone = ZoneId.of("America/New_York");
ZonedDateTime then = LocalDate.now(zone)
.atStartOfDay(zone)
.withHour(hour);
ZonedDateTime now = ZonedDateTime.now(zone);
System.out.println(now);
if (then.isAfter(now))
then = then.minusDays(1).withHour(hour);
long seconds = ChronoUnit.SECONDS.between(then, now);
System.out.println(seconds);
}
}
Output from a sample run:
2023-01-08T09:31:28.040819-05:00[America/New_York]
63088
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
Let's say startHour is given by the user (assume, that's in the 0-23 range). Now you may start with this:
import java.util.Calendar;
import static java.util.Calendar.*;
...
Calendar start = Calendar.getInstance();
if (start.get(HOUR_OF_DAY) < startHour) {
start.add(DAY_OF_MONTH, -1);
Use Joda-Time classes. You can do something like this:
DateTime now = new DateTime();
DateTime start;
int startHour = getStartHour();// user's choice
int nowHour = now.getHourOfDay();
if (nowHour < startHour) {
start = now.minusHours(24 - startHour + nowHour);
} else {
start = now.minusHours(nowHour - startHour);
}
Related
I was trying to get milliseconds from epoch until 2020.01.01. I used old method with Date and I also wanted to use new sexy LocalDate but two results I got are different:
long millisecondsInTheDay = 24 * 60 * 60 * 1000;
long millis1 = LocalDate.of(2020, Month.JANUARY, 1).toEpochDay() * millisecondsInTheDay; // 1577836800000
long millis2 = new Date(2020 - 1900, 0, 1).toInstant().toEpochMilli(); // 1577833200000
Difference is exactly one hour (3600_000 milliseconds). Why I get different result?
I don't want to comment on why you get a difference because I think that both of the original approaches are problematic. You need to pay close attention to things like time zones; and you really should avoid doing any sort of arithmetic on numerical values representing dates.
You need to pay special care to specify the points you are measuring between: if you want a number of milliseconds, presumably you really want to specify those points as instants in time. "1970" isn't an instant, it's a year-long period; "2020-01-01" isn't an instant either, but a period whose meaning shifts depending on time zone - there's roughly 48h-worth of instants where somewhere on the planet it is considered to be that date.
The correct way to do this (assuming you want milliseconds between epoch and the start of the day in your preferred timezone) is:
Duration between =
Duration.between(
Instant.EPOCH,
LocalDate.of(2020, Month.JANUARY, 1).atStartOfDay(zoneId));
long betweenMillis = between.toMillis(); // If you must - better to keep the type information that this is a Duration.
Note that you need to specify the zoneId, e.g. ZoneId.of("Europe/Warsaw"), because that affects when the start of the day is, and hence how many milliseconds.
Different time zones
Why I get different result?
Joachim Sauer said it already: This is because of different time zones. millis1 is the count of milliseconds until January 1, 2020 at 00:00 in UTC. millis2 counts until January 1, 2020 at 00:00 in your local time zone, presumably Europe/Warsaw. In winter Poland is at offset +01:00 from UTC, which explains the difference of 1 hour between the two. Everything agrees nicely. The epoch is one point in time and independent of time zone. It’s usually defined as January 1, 1970 at 00:00 in UTC.
That said I agree with Andy Turner that both ways to calculate are problematic.
A good calculation with java.time
Here’s my go, of course using java.time, the modern Java date and time API:
ZoneId targetZone = ZoneOffset.UTC;
long millis = LocalDate.of(2020, Month.JANUARY, 1).atStartOfDay(targetZone)
.toInstant()
.toEpochMilli();
System.out.println(millis);
Output:
1577836800000
If you did want your own time zone, just change the first line:
ZoneId targetZone = ZoneId.of("Europe/Warsaw");
1577833200000
The key is to use the same timezone (e.g. UTC) for both, the legacy and the modern API.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
long millisUsingJavaUtilDate = sdf.parse("2020.01.01")
.getTime();
long millisUsingJavaTime = LocalDate.of(2020, Month.JANUARY, 1)
.atStartOfDay(ZoneOffset.UTC)
.toInstant()
.toEpochMilli();
System.out.println(millisUsingJavaUtilDate);
System.out.println(millisUsingJavaTime);
}
}
Output:
1577836800000
1577836800000
Let's try with another timezone, America/New_York:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
long millisUsingJavaUtilDate = sdf.parse("2020.01.01")
.getTime();
long millisUsingJavaTime = LocalDate.of(2020, Month.JANUARY, 1)
.atStartOfDay(ZoneId.of("America/New_York"))
.toInstant()
.toEpochMilli();
System.out.println(millisUsingJavaUtilDate);
System.out.println(millisUsingJavaTime);
}
}
Output:
1577854800000
1577854800000
Learn more about the modern date-time API from Trail: Date Time.
Note that the legacy date-time API (java.util date-time types and their formatting API, SimpleDateFormat) are outdated and error-prone. It is recommended to stop using them completely and switch to java.time, the modern date-time API* .
* 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.
new Date( y , m , d ) uses default time zone
Some of the other Answers are correct and very useful. But I want to make very plain and simple where your code went wrong:
➥ The deprecated constructor of java.util.Date for year-month-day arguments implicitly applies your JVM’s current default time zone.
Take the first part of the key line in your code:
new Date(2020 - 1900, 0, 1).toInstant()
… where an Instant is always in UTC (an offset of zero hours-minutes-seconds), by definition. On my machine the current default time zone in my JVM is America/Los_Angeles. On your date and time, this zone was eight hours behind UTC.
So let's try these three lines of code code:
System.out.println(
ZoneId.systemDefault()
);
System.out.println(
new Date(2020 - 1900, 0, 1)
);
System.out.println(
new Date(2020 - 1900, 0, 1).toInstant()
);
When run, we see indeed that the moment represented by new Date is the first moment of that day as seen in the time zone America/Los_Angeles, colloquially known as PST. That zone on that date is eight hours behind UTC. We can see this fact in the third line, when calling toInstant has adjusted to UTC where the time-of-day is 8 AM.
America/Los_Angeles
Wed Jan 01 00:00:00 PST 2020
2020-01-01T08:00:00Z
Avoid Date
In the bigger picture, stop using Date class!
There are no benefits to be had by studying the behavior of java.util.Date. That class is absolutely terrible, written by people who did not understand date-time handling. Along with Calendar, java.sql.Date, and SimpleDateFormat, these classes should never be used.
These legacy classes were supplanted years ago by the java.time classes, defined in JSR 310. Sun, Oracle, and the JCP community unanimously gave up on these classes. So should you.
Your problem is here:
long millis1 = LocalDate.of(2020, Month.JANUARY, 1).toEpochDay() * millisecondsInTheDay; // 1577836800000
You use the LocalDate class, which gets for you the local time (in your timezone) while time in Java (in millisec) is the amount of time elapsed between 01.01.1970 UTC (Universal Coordinated Time) this is (at the date you requested, 01.01.2020 00:00:00 UTC):
1577836800000
The difference you get is due to the time offset observed at your local time (one hour, probably you are in central european time --CET--)
Edit:
By the way, I've seen in the answers (and in your code) that you use:
new Date(2020 - 1900, 0, 1);
This is very bad code. You are assuming that the above is equivalent to the difference in milliseconds that will be between the dates 2020.1.1 and 1900.1.1 and indeed, it represents the timestamp at date 120.1.1 this is the timestamp at the first of january of year one hundred and twenty (a.C) There's no distributive property between dates in new Date() operator. And if the years were all the same duration, this could be true... but they are not. A good way would be to use:
long millis = new Date(2020, 0, 1).getTime() - new Date(1900, 0, 1).getTime();
but the later is not equivalent to what is written above.
I'm looking to create a datetime stamp, then add 10 hours to it, then have a thread check to see if the time has elapsed.
I read through, Time comparison but it seems a bit complicated/convoluted for something so simple. Especially if your time comparison goes across midnight.
My understanding is that java's underlying datetime, is suppose to be a long, if this is true, is there a simple way to add another long to it, such as the number equivalent of 10 hours? Or some other means such as adding two dates?
Note: The solution needs to be part of core java, can't be part of a 3rd party lib.
You can use a Calendar to perform that math,
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR, 10); // Add 10 hours.
Date date2 = cal.getTime(); // Now plus 10 hours.
Date date = new Date(); // Now.
You can use the Date.getTime() method to obtain the underlying timestamp, the timestamp is basically the number of milliseconds elapsed since a defined base instant (1970-01-01 00:00:00 IIRC).
System.currentTimeMillis() allows you the get the "now" instant directly, without any detours using Date, Calendar and the like.
The timestamp can then be manipulated basic math:
timestamp += TimeUnit.MILLISECONDS.convert(10, TimeUnit.HOURS);
Example of adding 10 hours:
long nowInMilliSince1970 = System.currentTimeMillis();
long tenHoursAsMilli = TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES);
long tenHoursLater = nowInMilliSince1970 + tenHoursAsMilli;
System.out.println("now in milliseconds: \t\t" + nowInMilliSince1970);
System.out.println("10 hours in milliseconds: \t" + tenHoursAsMilli);
System.out.println("10 hours from now: \t\t" + tenHoursLater);
Checking if the timestamp is in the past is as easy as:
if (timestamp < System.currentTimeMillis()) {
System.out.println("timestamp is in the past");
}
Do note that direct timestamp math has no concept of daylight saving and time zones. If you want that, use a Calendar for math - Calendar implements the dirty exceptional rules for that.
Another way of achieving it using just JDK built in stuff is:
long tenHoursFromNow = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(10);
and then in your Thread you would check:
if(System.currentTimeMillis() > tenHoursFromNow)
{
//Do something as the time has elapsed
}
Although I would argue that the use of Calendar and Date is clearer as to what the intention of your code is trying to achieve.
The bundled java.util.Date and .Calendar are notoriously troublesome. They really should be avoided.
You stated a requirement of no added libraries. So see the java.time part of my answer, using the new package newly added to Java 8. But I urge you to reconsider your reluctance to add a library, especially if you cannot move to Java 8 yet; j.u.Date/Calendar really are that bad.
Both libraries handle anomalies such as Daylight Saving Time.
Consider specifying a time zone rather than rely on the JVM's default. Generally best to work in UTC, and then translate to a local time zone for presentation to the user.
java.time
The java.time package is newly added to Java 8. Inspired by Joda-Time but re-architected. Defined by JSR 310. Extended by the threeten-extra project.
ZonedDateTime tenHoursLater = ZonedDateTime.now().plusHours( 10 );
Joda-Time
Using the Joda-Time 2.3 library.
DateTime tenHoursLater = DateTime.now().plusHours( 10 );
For more info on this kind of use of Joda-Time, see my answer to a similar question.
i am trying Joda time in java using the latest version 2.2 i have written a small snippet here is my code
public static void main(String[] args)
{
BoilerTester clazz = new BoilerTester();
Calendar today = Calendar.getInstance();
Calendar born = Calendar.getInstance();
//when returns 0 is 10363 when returning 1 = 10362 just a millisecond what have to do with days??
System.out.println(Math.abs(today.getTimeInMillis()-born.getTimeInMillis()));
born.set(1984,10,20);
clazz.compute(born,today);
}
private void compute(Calendar born, Calendar today)
{
System.out.println("JODA: " + org.joda.time.Days.daysBetween(new org.joda.time.DateTime(born.getTime()),new org.joda.time.DateTime(today.getTime())).getDays());
}
when i run the source code i am getting this values
JODA: 10363
later I run the same code and I am getting
JODA: 10362
Yes i have run maybe 2 or 3 times the same code to get different values but why this???
thanks a lot.
My guess is that sometimes, the today and born dates are on the exact same millisecond, and sometimes they differ by a few milliseconds (the time that elapses between the first call to Calendar.getInstance() and the second one). Since getDays() returns the number of complete days, a few milliseconds could make a difference.
I was writing my answer but JB Nizet was faster (he is absolutely right on what is happening). If you want to get rid of this kind of problems, you should leverage the concept of partial in joda-time:
A partial in Joda-Time is a partial date and time representation. All implementations represent local dates and times, and do not reference a time zone. As such, they only partially represent a date or time in the datetime continuum.
For example, with a LocalDate:
LocalDate born = new LocalDate(1984, 11, 20); // BE CAREFUL!: months in JDK are 0-11, but in Joda-Time are 1-12
System.out.println(Days.daysBetween(born, LocalDate.now()).getDays());
With this "partial" representation, you are not using hours, minutes, seconds or milliseconds internally, so you cannot face this problem due to milliseconds.
You're running a mix of JDK and Joda -- don't do that -- Joda replaces the JDK time classes completely.
I am able to convert date to days using the below code.
SimpleDateFormat sfd = new SimpleDateFormat("yyyy-MM-dd");
String s1 = sfd.format(dateObj);
String a1 [] = s1.split("-");
int year = Integer.parseInt(a1[0].toString());
int month = Integer.parseInt(a1[1])-1;
int day = Integer.parseInt((a1[2]));
Calendar c1 = Calendar.getInstance();
c1.set(year,month,day);
days = c1.getTime().getTime()/(24*60*60*1000);
The above code works accurately in my system which is windows with timezone GMT +5.30.
However the same code in EST or Pacific timezone adds a day by 1 to final result when the time is 20.00 in the system.
What could be the issue ?
Do we need to set Timezone explicitly in the code ?
input dates does not hold any time stamp ..
is it correct to store in java.util.Date instead of java.sql.Date?
EDIT: As per Alex's comment, it's possible that the problems with the start of your code have blinded me to your real aim.
A Date represents an instant in time. That can fall on different dates depending on the time zone, but how do you want that to affect things? Do you want the number of days since the Unix epoch (which is always UTC) or the number of days since the 1st January 1970 in a particular time zone? Why do you want this "number of days" instead of a representation of a date such as LocalDate? What's the use case here?
EDIT: If you just want to know the number of days since the Unix epoch, you can skip most of this:
days = dateObj.getTime() / (24 * 60 * 60 * 1000);
You shouldn't be going through formatting at all just to get the year / month / day. Just create a Calendar, set the relevant time zone, call setTime with the dateObj you've already got, and then clear the hour/minute/second part of the calendar.
However, you should explicitly specify which time zone you want to consider - a Date represents an instant in time, which will mean different dates in different time zones.
You should also consider using Joda Time which makes all of this simpler and has a specific type for dates (LocalDate). That would also make it easy to find the number of days between the Unix epoch and a particular date without performing the division yourself.
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:
You can convert the object of java.util.Date to Instant using Date#toInstant and then you can find the number of days from now until this date using ChronoUnit#between.
Demo:
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.GregorianCalendar;
public class Main {
public static void main(String[] args) {
// A sample java.util.Date
Date dateObj = GregorianCalendar.from(ZonedDateTime.of(2021, 10, 2, 22, 25, 0, 0, ZoneOffset.UTC)).getTime();
Instant instant = dateObj.toInstant();
// Difference between now and the given java.util.Date
System.out.println(ChronoUnit.DAYS.between(Instant.now(), instant));
}
}
Output:
99
ONLINE DEMO
Note that the above code calculates the number of days between two moments/instants represented in UTC. If you have date-time values local to a particular timezone, you need to specify the corresponding ZoneId.
Demo:
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.GregorianCalendar;
public class Main {
public static void main(String[] args) {
ZoneId tz = ZoneId.of("Australia/Brisbane");
// A sample java.util.Date representing the local date and time values in Australia/Brisbane
Date dateObj = GregorianCalendar.from(ZonedDateTime.of(2021, 10, 2, 22, 25, 0, 0, tz)).getTime();
// Difference between now in Australia/Brisbane and the given java.util.Date
System.out.println(ChronoUnit.DAYS.between(Instant.now().atZone(tz), dateObj.toInstant().atZone(tz)));
}
}
Output:
98
ONLINE DEMO
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.
What time is the start of a day, say 01/01/2010?
Is it 00:00:00:000 ? or is that midnight?
[edit]
It might be a stupid question but I'm confused because I used Calendar.set(Calendar.HOUR, 0) but this gives me a time of 12:00:00.
and now I've realised I should be using HOUR_OF_DAY
The start of the day isn't always midnight. It can depend on the time zone and date. (If the clock moves forward an hour at the start of the day, it will start at 1am.)
That's why Joda-Time has things like LocalDate.toDateTimeAtStartOfDay - and they're well worth using.
But yes, normally it's at 00:00:00 which is midnight. (This can also be formatted as "12am" depending on your locale etc.)
java.time
Normally, the start of the date is 00:00 hours but it may vary because of DST. Therefore, instead of assuming it to be 00:00 hours, the safest option is to use LocalDate#atStartOfDay(ZoneId zone).
Demo:
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("M/d/u", Locale.ENGLISH);
LocalDate date = LocalDate.parse("01/01/2010", dtf);
// In JVM's timezone
ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault());
System.out.println(startOfDay);
// In custom timezone
startOfDay = date.atStartOfDay(ZoneId.of("Africa/Johannesburg"));
System.out.println(startOfDay);
}
}
Output:
2010-01-01T00:00Z[Europe/London]
2010-01-01T00:00+02:00[Africa/Johannesburg]
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.
ZonedDateTime from java.time
Like Arvind Kumar Avinash already does in a good answer, I recommend that you use java.time, the modern Java date and time API, for your date and time work.
If you had got a LocalDate or a string holding a date without time of day, that answer shows you how to get the start of the day (the first moment of the day). If you had already got a ZonedDateTime, you may simply use its truncatedTo method. Let’s take one of those interesting examples where the clocks are turned forward at 00:00 so the first moment of the day is 01:00:
ZonedDateTime zdt = ZonedDateTime.of(
2000, 9, 17, 15, 45, 56, 789000000, ZoneId.of("Asia/Dili"));
System.out.println("We got date and time: " + zdt);
ZonedDateTime startOfDay = zdt.truncatedTo(ChronoUnit.DAYS);
System.out.println("Start of day is: " + startOfDay);
Output:
We got date and time: 2000-09-17T15:45:56.789+09:00[Asia/Dili]
Start of day is: 2000-09-17T01:00+09:00[Asia/Dili]
What went wrong in your code?
You’ve already said it in an edit to the question, but it deserves to be mentioned in an answer too: Calendar.HOUR refers to, from the documentation:
Field number for get and set indicating the hour of the morning or
afternoon. HOUR is used for the 12-hour clock (0 - 11). …
So if your Calendar was already holding a time in the afternoon (12 noon or later), setting HOUR to 0 gives you 12 noon (12:00 on a 24 hour clock), not 12 midnight (00:00 on a 24 hour clock). Except that the time of the hour may still be non-zero, so you may also get, for example, 12:34:45.567. The Calendar class was cumbersome to work with.
In any case the Calendar class was poorly designed and is long outdated, so you shouldn’t need to worry; just don’t use that class anymore.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of Calendar.HOUR.