I'm trying to write code to run at 1 am. The idea is to find the amount of time to next day in milliseconds, then do a sleep on that time
I do this by getting the reminder of the current time divided by to days time.
remainder= current time % 8640000
where current time is gotten from Time.getTimeInMillis();
I got 61175831
to get days by divide it by (606024)
witch gives me 16 hours
It's 1pm so 13+16=30 or 6am next day not 1 am in morning
delayTime=24*60*60*1000; // i day
Calendar Time = Calendar.getInstance();
long delay=TimeToNumber%delayTime;
ling days=delay/1000;
days=days/60;
days=days/60
Got 16 for days
Without knowing more about your usage scenario, I can't really comment on whether your suggested approach is the best way to do the actual scheduling. However, I can answer the question of how to determine the number of milliseconds until 1:00 AM.
ZonedDateTime now = ZonedDateTime.now();
LocalTime targetTime = LocalTime.parse("01:00");
ZonedDateTime targetDateTime;
if (now.toLocalTime().isBefore(targetTime)) {
targetDateTime = now.with(targetTime);
} else {
targetDateTime = now.plusDays(1).with(targetTime);
}
long millis = Duration.between(now, targetDateTime).toMillis();
Explanation
The Calendar API is a legacy API that comes with a bunch of challenges and gotchas with using it. Additionally, by attempting to do the calculations yourself mathematically, you're missing the various nuances that are automatically handled for you by the libraries (such as daylight saving time shifts and the like). For these reasons, I strongly suggest using the newer java.time API.
For this particular question, you need to determine the number of milliseconds until the next 1:00 AM. If the time is before 1:00 in the current date, you want to return the current date's 1:00. If it's after 1:00, you want to return tomorrow's 1:00.
ZonedDateTime is a good choice to represent 1:00 today/tomorrow, since in addition to the date & time, a time zone is needed in order to correlate the datetime with a specific instant in time.
Retrieving the next 1:00 can be achieved by comparing the current LocalTime with a LocalTime of 1:00 AM:
ZonedDateTime now = ZonedDateTime.now();
LocalTime targetTime = LocalTime.parse("01:00");
ZonedDateTime targetDateTime;
if (now.toLocalTime().isBefore(targetTime)) {
targetDateTime = now.with(targetTime);
} else {
targetDateTime = now.plusDays(1).with(targetTime);
}
This is assuming you're using the system default time zone. If you want to use a different time zone, this can be specified in the call to ZonedDateTime.now():
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));
With this ZonedDateTime representing the next 1:00 AM, you can use a Duration between the current time and that time, and then get the number of milliseconds in the duration:
long millis = Duration.between(now, targetDateTime).toMillis();
I'm running the below expressions on December 7th, 2018.
I'm seeing a discrepancy whereby this:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30)
returns (correctly):
2018-11-07T22:44:11.242576-05:00[America/New_York]
whereas conversion to an instant:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30).toInstant()
seems to mess up the result by adding an extra day to it:
2018-11-08T03:58:01.724728Z
I need an instant conversion to use its result in the following code as Date:
... = Date.from(t.toInstant())
An equivalent Python code (Django) works correctly:
datetime.datetime.now('America/New_York')+datetime.timedelta(days=-30)
evaluating to: datetime: 2018-11-07 20:13:55.063888-05:00
What's causing the discrepancy?
What should I use so that Java conversion to Date resulted in the November 7th being returned, just like in Python's case? Basically, I'm looking to an equivalent translation of that Python code into Java, or in pseudocode:
`datetime.X = datetime.now(deployment_zone) - (N_days)`,
where `deployment_zone` is configurable (i.e. `America/New_York`)
`N_days` is configurable (i.e. 30)
Update for #Basil Bourque:
When I formulated the original question, I (per SO rules) tried to simplify it to a digestible form which probably destroyed most of the necessary context making it vague. Let me try again.
As I explained in the comments, I'm converting the existing Python code (which is more actively maintained and which client wants to keep intact) to existing Java code (legacy that has not been properly maintained and strayed away from the Python's logic some time back). Both code bases need to be functionally on par with each other. Java needs to do what Python is already doing.
Python code is as follows (I'm lumping all into one place for succinctness, in reality it's distributed across a couple of files):
analytics.time_zone=America/New_York
TIME_ZONE = props.getProperty('analytics.time_zone', 'UTC')
TZ = pytz.timezone(TIME_ZONE)
def days_back(num_days=0):
adjusted_datetime = datetime.datetime.now(TZ)+datetime.timedelta(days=-num_days)
return DateRangeUtil.get_start_of_day(adjusted_datetime)
class DateRangeUtil():
#staticmethod
def get_start_of_day(date):
return date.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0)
which basically takes the configured time zone, in which it obtains the current instant, subtracts a specified number of days from it, converts it to the beginning of that date and thus receives the lower bound of the range to use while querying the DB, something like Start time: datetime: 2018-11-07 20:13:55.063888-05:00
When I started on the Java side, it had:
public final static DateRange parse(String dateRange) {
//.....
int days = ...
return new DateRange(toBeginningOfDay(daysBack(days)), toEndOfDay(daysBack(0)));
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
private final static Date toBeginningOfDay(Date d)
{
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(HOUR_OF_DAY,0);
c.set(MINUTE,0);
c.set(SECOND,0);
c.set(MILLISECOND, 0);
return c.getTime();
}
private final static Date toDate(LocalDateTime t) {
return Date.from(t.atZone(ZoneId.systemDefault()).toInstant());
}
That code didn't work and introduced the discrepancy which I describe in my original question. I started experimenting and introduced ZonedDateTime into the picture. While investigating, I found that it's the call to .toInstant() that seems to be a culprit and wanted to understand what's behind it in more depth.
In his answer, #ernest_k suggested a solution which seemed to have worked, but I still didn't quite understood which is clear from questions in the comments to his response.
The changes I made based on #ernest_k response are as follows:
private final static Date daysBack(int days) {
return toDate(ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(days).toLocalDateTime());
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
This seems to produce the desired outcome: However conversion from local to zoned and then back again seemed too much, so I experimented a bit more and found that simply the LocalDateTime does the trick as well:
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
I can see that LocalDate (and perhaps LocalDateTime) has a convenient atStartOfDay() which seems to be a fitting candidate for elimination of Dates out of the picture while replacing the legacy toBeginningOfDay(Date d) method above. Not sure it's doing the same thing - I haven't yet experimented with that idea, so the suggestions are most welcome.
So, with all of the tribulations above, my question started around toInstant() behavior, and when it's passed a zone id, whether it converts TO an instant in that zone, or FROM it, or what?
I guess for the situation I'm describing we only care that the lower time bound in the DB query is formed by comparing some consistent marker of current time (its upper bound) to what it was in the same place (time zone?) in the past N days, so comparing it with UTC should server the purpose.
Does that then make passing the zone in unnecessary?
Now, that a solution seems to have been found, the question revolves around the soundness of the approach described above and the solution that's been stumbled upon - is it the most optimal one, best practices around Java timing libs, etc. The code needs to work for any time zone in which the code bases will end up being deployed, that's why the zone is passed in via configuration.
Also, I wonder if things change when/if the DB itself is deployed off-premise from the rest of the codebase and is configured to persist data in some other time zone. But that might be another question.
tl;dr
ZonedDateTime.toInstant() adjusts a moment from a time zone to UTC. You end up with the same moment, different wall-clock time, and possibly a different date for the same simultaneous point on the timeline. What you are seeing is not a problem, not a discrepancy.
Your problem is not with subtracting 30 days. The real problems:
Not understanding that time zone affects the date
Conflating dates with days
Furthermore, your Question is vague. Saying “30 days ago” can mean at least three different things:
30 * 24 hours
A range from 22:44 thirty calendar days ago in New York time zone to 22:44 now in New York time
The entire day today as seen in New York and the entire days going back 30 days on the calendar as seen in New York.
All three possibilities are covered below, with example code, labeled with ➥.
⑦🕥 🇺🇸📞 ↔ 📞🇮🇸 ⑧🕞
On the 7th of December, shortly before midnight (22:44), Alice in her New York apartment decides to call her friend Bob in Reykjavík, Iceland. Bob can't believe his phone is ringing, and looking over at the clock on his bedside table sees the time is almost 4 AM (03:44). And Bob's fancy digital clock shows the date as the 8th of December, not the 7th. Same simultaneous moment, same point on the timeline, different wall-clock time, different date.
The people of Iceland use UTC as their time zone, year-round. New York is five hours behind UTC in December 2018, and so five hours behind Iceland. In New York it is “yesterday” the 7th while in Iceland it is “tomorrow” the 8th. Different dates, same moment.
So forget about subtracting the thirty days. Any time you take a moment in New York that is close to midnight, and then adjust to UTC, you will be moving the date forward.
No discrepancy, no extra day added. For any given moment, the date varies around the globe by time zone. With a range in time zones of about 26-27 hours, it is always “tomorrow” and “yesterday” somewhere.
Another Answer suggests involving LocalDateTime into this problem. That is ill-advised. That class purposely lacks any concept of time zone or offset-from-UTC. That means a LocalDateTime cannot represent a moment. A LocalDateTime represents potential moments along the range of 26-27 hours mentioned above. Makes no sense to involve that class here.
Instead, use OffsetDateTime for a moment viewed with an offset-from-UTC, versus [ZonedDateTime][2] which uses a time zone.
What is the difference between an offset and zone? An offset is merely a number of hours-minutes-seconds, nothing more, nothing less. A zone, in contrast, is much more. A zone is a history of the past, present, and future changes to the offset used by the people of particular region. So a time zone is always preferable to a mere offset, as it brings more information. If you want UTC specifically, you need only an offset, an offset of zero hours-minutes-seconds.
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from a time zone to UTC.
The zdt and odt seen here both represent the same moment, the same point on the timeline, different wall-clock time, like Alice and Bob example above.
Days != Dates
If you want to query for a range of thirty days ago, you must define what you mean by “days”.
Days
➥ Do you mean 30 chunks of 24-hour long spans of time? If so, work with Instant. This class represents a moment in UTC, always in UTC.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Instant instantNow = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
Instant instantThirtyDaysAgo = instantNow.minus( 30 , ChronoUnit.DAYS ) ; // Subtract ( 30 * 24 hours ) without regard for dates.
You may be able to exchange an Instant with your database via your JDBC driver. But Instant is optional, while support for OffsetDateTime is required by JDBC 4.2 and later. If that is the case, let's re-write that code.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
OffsetDateTime odtNow = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
OffsetDateTime odtThirtyDaysAgo = odtNow.minusDays( 30 ) ;
Your SQL might be something like the following.
Note what we are using the Half-Open approach to defining a span-of-time, where the beginning is inclusive while the ending is exclusive. This is generally best practice, as it avoid the problem of finding the infinitely divisible last moment, and it provides for neatly abutting spans without gaps. So we do not use the SQL command BETWEEN, being fully-closed (inclusive on both ends).
SELECT * FROM event_ WHERE when_ >= ? AND when_ < ? ;
Set values for the placeholders in your prepared statement.
myPreparedStatement.setObject( 1 , odtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , odtNow ) ;
Dates
➥ If by “30 days ago” you meant 30 boxes on the calendar hanging on the wall in a New York office, that is a very different problem.
Same time-of-day
And if so, do you mean from the current moment and moving back 30 days to the same time-of-day?
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
ZonedDateTime zdtThirtyDaysAgo = zdtNow.minusDays( 30 ) ; // `ZonedDateTime` will try to keep the same time-of-day but will adjust if that time on that date in that zone is not valid.
With the code seen above, the ZonedDateTime class will try to use the same time-of-day on the earlier date. But that time may not be valid on that date in that zone, because of anomalies such as Daylight Saving Time (DST) cutover. In such an anomaly, the ZonedDateTime class adjusts to a valid time. Be sure to study the JavaDoc to understand the algorithm and to see if it suits your business rules.
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , zdtNow ) ;
Entire day
➥ Or by “30 days ago” do you mean dates, and by dates you mean all-day-long?
If so, we need to focus on the date-only value, by using LocalDate class, without a time-of-day and without a time zone.
ZoneId z = ZoneId.of( "America/New_York" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate tomorrow = today.plusDays( 1 ) ;
LocalDate thirtyDaysAgo = tomorrow.minusDays( 30 ) ;
Now we need to go from the date to a specific moment by assigning a time-of-day and a time zone. We want the time to be the first moment of the day. Do not assume that means 00:00. Because of anomalies such as DST, the day may start at another time such as 01:00. Let java.time determine the first moment of the day on that date in that zone.
ZonedDateTime zdtStart = thirtyDaysAgo.atStartOfDay( z ) ;
ZonedDateTime zdtStop = tomorrow.atStartOfDay( z ) ;
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtStart ) ;
myPreparedStatement.setObject( 2 , zdtStop ) ;
That "extra day" is not really an extra day. 2018-11-07T22:44:11 in New York is equivalent to 2018-11-08T03:58:01 in UTC (it's the same point in time). The difference is just 5 hours, not a day (and when I google this, I see New York is GMT-5).
ZonedDateTime#toInstant returns an Instant instance representing the same point in time (in UTC):
Converts this date-time to an Instant.
This returns an Instant representing the same point on the time-line as this date-time. The calculation combines the local date-time and offset.
If you want to not use the offset when converting to instant, then you should perhaps use LocalDateTime:
ZonedDateTime.now(ZoneId.of("America/New_York"))
.toLocalDateTime()
.toInstant(ZoneOffset.UTC)
This tells it to convert as though it were already UTC time (but a warning is appropriate here: this changes the date/time value)
First, avoid the need for an old-fashioned Date if you can. java.time, the modern Java date and time API, gives you all the functionality you need.
Sometimes we do need a Date for a legacy API that we cannot change or don’t want to upgrade just now. Java is giving you what I think you want. Demonstration:
ZonedDateTime nov7 = ZonedDateTime.of(2018, 11, 7, 22, 44, 0, 0,
ZoneId.of("America/New_York"));
Instant inst = nov7.toInstant();
System.out.println("As Instant: " + inst);
Date oldFashionedDate = Date.from(inst);
System.out.println("As Date: " + oldFashionedDate);
Output from this was:
As Instant: 2018-11-08T03:44:00Z
As Date: Wed Nov 07 22:44:00 EST 2018
Admitted, to get this output I had to change my JVM’s default time zone to America/New_York first.
Date and Instant are roughly equivalent but print differently. Meaning their toString methods behave differently, which may be confusing. Each is a point in time, none of them is a date (despite the name of one of them). It is never the same date in all time zones.
Date.toString picks up your JVM’s time zone setting and uses it for generating the string it returns. Instant.toString on the other hand always uses UTC for this purpose. This is why the same point in time is printed with different date and time. Fortunately they both also print a bit of time zone information so the difference is at least visible. Date prints EST, which, albeit ambiguous, in this case means Eastern Standard Time. Instant prints Z for offset zero from UTC or “Zulu time”.
I am trying to use Java to sum two times.
Lets say i have these LocalTimes:
LocalTime L1 = LocalTime.parse("2:10");
LocalTime L2 = LocalTime.parse("13:20");
Is there any fancy LocalTime method which makes it possible to sum hours and get 15:30 (L1+L2)
(Note: I was looking for something in the time or date package), without String butchering.
Basically, LocalTime marks a point in time, not a duration. You cannot add two points in time - it doesn't make sense.
LocalTime does have a plus() method, but it requires a TemporalAmount (a Period or Duration) and not a LocalTime. You can do what you want by converting a LocalTime to a Duration by using Duration.ofNanos(localTime.toNanoOfDay()), so you'd do something like l1.plus(Duration.ofNanos(l2.toNanoOfDay())) (this basically means you're treating the second LocalTime not as a point in time, but as the amount of time that elapsed since the start of the day)
The Answer by Wilkin is correct. I'll elaborate.
You have conflated two key concepts:
Point in time, a moment, a point on the timeline
Span of time, an amount of time not attached to the timeline.
An example of the first is agreeing to meet you for lunch next Wednesday at 12:30 PM in Africa/Casablanca time zone. An example of the second is a school’s policy that the students’ lunch period lasts 50 minutes.
For the first, use the Instant (always in UTC), OffsetDateTime (offset from UTC as a count of hours and minutes), or ZonedDateTime (a time zone such as Pacific/Auckland assigned) classes.
For the second, use the Period (a count of years, months, and days) or Duration (a total number of whole seconds plus a fractional second) classes.
For a date-only value without time of day and without time zone, use LocalDate. For a time-of-day without a date and without a time zone, use LocalTime.
Your examples suggest that you had two events occur, one elapsing a couple minutes and one taking thirteen minutes. So we need the Duration class. The LocalTime class does not apply to your problem.
You confusingly use time-of-day notation to represent such spans of time. Better to use standard ISO 8601 formats for such strings. The standard format is PnYnMnDTnHnMnS where the P marks the beginning and the T separates the years-months-days from the hours-minutes-seconds.
The java.time classes use the standard formats by default when parsing or generating strings.
Duration d1 = Duration.parse( "PT2M10S" ) ;
Duration d2 = Duration.parse( "PT13M20S" ) ;
Duration d3 = d1.plus( d2 ) ;
d3.toString(): PT15M30S
I have two times in hours and minutes.
time[0]: hour1
time[1]: minutes1
time[2]: hour2
time[3]: minutes2
I've created this formula to calculate the difference in time in minutes:
((time[2] % 12 - time[0] % 12) * 60) + (time[3] - time[1])
I was wondering if there are any edge cases to this. In addition, what is the paradigm you would follow to create this formula (although it is very basic)?
You could express your times with the Date class instead, then calculate the difference and then express it in the time unit of your choice.
With this method, you will avoid a lot of tricky cases (difference between two times on two different days, time change, etc.).
I recommend you the reading of this post and this post but there are many answers to this same exact question on StackOverflow ;)
Note: before using Date, have a look to this excellent post: What's wrong with Java Date & Time API?
Your code assumes days are 24 hours long. Not all days are 24-hours long. Anomalies such as Daylight Saving Time (DST) mean days vary in length.
Also, we have classes already built for this. No need to roll your own. The LocalTime class represents a time-of-day without a date and without a time zone. A Duration represents a span of time not attached to the timeline.
LocalTime start = LocalTime.of( 8 , 0 ) ;
LocalTime stop = LocalTime.of( 14 , 0 ) ;
Duration d = Duration.between( start , stop );
long minutes = d.toMinutes() ; // Entire duration as a total number of minutes.
That code too pretends that days are 24 hours long.
For realistic spans of time, use the ZonedDateTime class to include a date and time zone along with your time-of-day.
Hello
I wanna create app that has different behaviors if it is morning, noon, afternoon, evening.
Now i wanna set some variables with the time for each
For example noon = 12:00
Now i wanna compare current time with them and see if it is morning for example, and to calculate how much till noon 12:00 - current time.
Now I've seen several examples with different dates, but i want to compare only by hour.
Calendar cal=GregorianCalendar.getInstance();
int hour = cal.get(Calendar.HOUR);
Then compare the hour.
This will work for your local time zone
You can use the joda time hoursBetween or you can use Java calendar class. I would recommend using JodaTime.
Using the Java Calendar class:
Calendar future = Calendar.getInstance(); //future time
future.set(Calendar.YEAR, 2011);
future.set(Calendar.MONTH, 0);
future.set(Calendar.DATE,27);
future.set(Calendar.HOUR_OF_DAY,17);
//get current time
Calendar now = Calendar.getInstance();
//time difference between now and future in hours
long hoursDiff = (future.getTimeInMillis() - now.getTimeInMillis())/(60 * 60 * 1000);
System.out.println("Difference in hours is ="+hoursDiff);//prints 2 since it's 3 pm here
This does not factor in day light savings and compares against your LOCAL time zone.
Using Joda Time hoursBetween:
DateTime futureDate = new DateTime(future.getTime());
DateTime current = new DateTime(now.getTime());
int difference = Hours.hoursBetween(current,futureDate).getHours();
System.out.println("Difference in hours is ="+difference);
Also look at this question and this question.
tl;dr
if (
LocalTime.now( ZoneId.of( "Africa/Tunis” ) )
.isBefore( LocalTime.of( 12 , 0 ) )
) {
… // Do morning stuff.
}
java.time
The other answers were correct but use outmoded classes. The java.util.Date/.Calendar classes have been supplanted by the java.time framework built into Java 8 and later.
The LocalTime class represents a time-of-day without a date and without a time zone.
Make some constants for your definition of "morning", "afternoon", and so on. In real work I would use an enum. But I'll use a simple variable here for the demo.
LocalTime noon = LocalTime.of( 12 , 0 );
Time zone is crucial to interpreting time-of-day. A time-of-day only has meaning in the context of a particular time zone. If not specified, your JVM’s current default time zone will automatically be applied silently. I strongly recommend you instead always explicitly specify the desired/expected time zone. You can think of a ZonedDateTime as being the combination of an Instant plus a time zone (ZoneId).
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now( zoneId );
We can extract a LocalTime from the ZonedDateTime according to its assigned time zone.
LocalTime nowLocalTime = now.toLocalTime();
Compare to the target time.
Boolean isMorning = nowLocalTime.isBefore( noon );
Use the Duration class to represent a span of time as a total number of seconds plus a fraction of a second in nanoseconds.
ZonedDateTime todayNoon = now.adjustInto( noon );
Duration untilNoon = Duration.between( now , todayNoon );
The default output of Duration::toString is a string in a format defined by ISO 8601. Example PT38M2S which thirty-eight minutes and two seconds. You can also ask for number of minutes, etc.
By getting a Duration from ZonedDateTime objects we will get an accurate result accounting for anomalies such as Daylight Saving Time (DST). If you would rather use a generic 24-hour day calculation, pass the LocalTime objects to Duration.between.
You can use GregorianCalendar to do this. Create a new GregorianCalendar and set the month, day, and year to some constant value. Set the hour to whatever time you're interested in, ie 12:00 noon. Now just getTimeInMillis() and store that value. Later, you can create another GregorianCalendar with the no-arg version to get the current time. Set the month, day, and year to the same constant value as your benchmark value, and then just compare the getTimeInMillis() again to see if it is before, equal to, or after the reference time of day.