// someTime is epoch in millis (UTC)
final long timeNow = new Date().getTime();
final long midnight = timeNow - timeNow % (3600 * 24 * 1000L);
final long yesterdayMidnight = midnight - (3600 * 24 * 1000L);
// check if same day.
if (someTime >= midnight)
// do something
// check if yesterday
if (someTime >= yesterdayMidnight)
Edited: My purpose is to check whether someTime is in the same day or in the previous day without doing too much heavyweight stuff.
Does this account for day light savings and why? If not, what's the simplest logic?
Your current code doesn't do anything with the local time zone - everything is in UTC, effectively (certainly in terms of your code, which is dealing in "milliseconds since the Unix epoch").
If you want to make your code time-zone-sensitive, you should use (in order of preference):
Java 8's java.time (look at ZonedDateTime and Clock for example)
Joda Time
java.util.Calendar with java.util.TimeZone
Use higher-level abstractions where possible - your code should do as little low-level manipulation of time as possible.
EDIT: Now that we know the purpose, here's an example implementation in Joda Time:
public void calculate(Instant now, Instant then, DateTimeZone zone) {
LocalDate today = new LocalDate(now, zone);
LocalDate otherDay = new LocalDate(then, zone);
if (otherDay.equals(today)) {
// Today day
} else if (otherDay.equals(today.minusDays(1)) {
// Yesterday
} else {
// Neither today nor yesterday
}
}
Note how there's nothing low level here - we're just working out which date each value (now and then) falls in within the given time zone, and then comparing those.
Your check will fail in some cases having in mind daylight savings. Let's assume it is now 5 o' clock on the day when daylight saving happens and at 3 o'clock we've switched the clock forward. Therefor only 4 hours have passed since midnight but it is in fact 5. So midnight in your code will be a time 5 hours ago. This means that if someTime is between 5 hours ago and 4 hours ago(e.g. 4 hours and a half ago) when it's in fact been yesterday your algorithm will report it has been today.
That does not seem to be correct from daylight savings point. First, what's the timezone of someTime date? What about the days/nights(in fact) when the daylight savings take place (one hour +/-)?
If you use Joda lib there is a convenient method DateTime.isBefore() that will return whether one date is before another.
Related
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 unsure whether this can be made more efficient or not, but I need to get the number of days that have passed since a unix/epoch timestamp, where the time itself is not a factor, only the date compared to now.
Example 1:
Timestamp is : 3rd September 14:35
Compared to now which is: 4th September 00:35
Days difference = 1
Example 2:
Timestamp is: 3rd September 23:55
Compared to now which is: 4th September 00:35
Days difference = 1
Example 3:
Timestamp is: 2nd September 02:23
Compared to now which is: 4th September 00:35
Days difference = 2
To get this, I have the following code:
String epoch = "1599134401" // the unix/epoch timestamp in seconds
Long epochMillis = Long.valueOf(epoch) * 1000;
Date epochDateObj = new Date(epochMillis);
Calendar tsCal = Calendar.getInstance();
tsCal.setTime(epochDateObj);
tsCal.set(Calendar.HOUR_OF_DAY, 0);
tsCal.set(Calendar.MINUTE, 0);
tsCal.set(Calendar.SECOND, 0);
tsCal.set(Calendar.MILLISECOND, 0);
Calendar today = Calendar.getInstance();
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);
long diffInMillies = Math.abs(today.getTime().getTime() - tsCal.getTime().getTime());
long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
if(diff > 1) {
return diff + " days";
} else {
return diff + " day";
}
The above code works, but to me, it seems quite elaborate for such a rather small thing as this.
Any suggestions for optimizing it? Maybe there's some functionality I don't know about. Its an Android app which is using a rather old SDK (back to Android 4.1).
Days are quite fundamentally a human concept. They involve politics, opinion, confusion, timezones, eras, epochs, and other very hairy concepts. java.util.Date has no snowball's chance in hades to do it right. Nor does calendar.
Your only hope is a proper API, such as java.time.
Furthermore you need to clean up your question. What you're asking is impossible; you're comparing guns to grandmas. epoch-time is fundamentally a 'computer' concept - it refers solely to moments in time, it has no idea when, where, who, which political party, etc you are asking. Which is a problem, because without any of that information it is NOT possible to know what day it is. Seconds are more or less universal, but days are not. A day can be 23 hours or 25 hours, or 23 hours, 59 minutes and 59 seconds, or 24 hours and 1 second - sometimes whole days get skipped, etcetera. 'how long is a day' is not answerable without knowing who you ask and what timezone (and sometimes, political entity!) is used as context.
So, let's say you're asking someone in arizona. The answer will then depend rather a lot on where in arizona you ask and who you ask: You would need to (potentially) know whether the person you so happen to ask applies daylight savings time or not back in 1970 as well as in the 'target' time. This depends on whether you're asking when you're on an native american reservation within arizona or not, and/or if the person you're asking is sticking to NAR zones or not. Hence why I mentioned the politics thing, and why what you want is completely impossible.
java.time to the rescue which can actually represent the crazy mess!
Instant represents a moment in time. It's internally stored as epoch-millis and cannot tell you the day, month, year, era, hour, etc of that moment in time by itself. That's because.. well, that's because that's how reality works. If I snap my fingers right now, and I ask someone 'what time is it', it depends on where I am and where the person I'm asking is and what political parties they ascribe to, so it's not possible. But, you combine a Zone and an Instant and now we're getting somewhere.
LocalDateTime represents a time as a human would say it: A year/month/day + hour/minute/second. It is not possible to turn this into epochmillis for the same reason in reverse. And for the same reason, if you combine this with a Zone doors start opening.
ZonedDateTime tries to bridge the gap: It represents a time as a human would say it, but we code in the location (and political affiliations) of the human who said it. You can store this either as a LocalDateTime + TimeZone, or as an Instant+TimeZone (you don't need to know how it is implemented, of course). You can move from a ZDT to either Instant or LocalDateTime, of course, and this one can answer many questions.
Let's try to solve your problem:
String epoch = "1599134401"; // the unix/epoch timestamp in seconds
String where = "Europe/Berlin"; // what you want is impossible without this!!
Instant instant = Instant.ofEpochSecond(Long.valueOf(epoch));
ZonedDateTime target = instant.atZone(ZoneId.of(where));
ZonedDateTime today = ZonedDateTime.now(where);
long days = ChronoUnit.DAYS.between(target, today);
System.out.println(days);
As a general rule, if you start doing serious math on dates you're messing up and it won't work. Not that your tests will ever catch it of course; it'll go ape when the clocks go back or forward or some political party decides 5 days before it happens to end daylight savings time, or the client is in one place and your server is in another, etc - all stuff that tests rarely catch.
Proper use of java.time should usually mean you aren't doing much calculation, and so it is here, fortunately.
There’s already a very great and insightful answer by rzwitserloot, I highly recommend it. Just as a minor supplement I wanted to give you my go at the code. Still using java.time, the modern Java date and time API, of course.
ZoneId zone = ZoneId.of("Europe/Tirane");
DateTimeFormatter epochSecondFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.toFormatter();
String epoch = "1599134401"; // the unix/epoch timestamp in seconds
Instant then = epochSecondFormatter.parse(epoch, Instant::from);
LocalDate thatDay = then.atZone(zone).toLocalDate();
LocalDate today = LocalDate.now(zone);
long diff = ChronoUnit.DAYS.between(thatDay, today);
diff = Math.abs(diff);
if (diff == 1) {
System.out.println("" + diff + " day");
} else {
System.out.println("" + diff + " days");
}
When I ran the code just now, the output was:
1 day
Since you want to ignore the time of day, LocalDate is the correct class to use for the dates. A LocalDate is a date with time of day and without time zone.
In English (not being a native speaker, though) I prefer saying “0 days”, not “0 day”. So I have changed your condition for choosing between singular and plural.
Did your code work?
Your code gives inaccurate results in corner cases. TimeUnit is generally a fine enum for time unit conversions, but it assumes that a day is always 24 hours, which is not always the case, as rzwitserloot explained. The java.time code of that answer and of this one correctly takes transitions to and from summer time (DST) and other time anomalies into account.
Question: Does java.time work on Android 4.1?
java.time works nicely on both older and newer Android devices. It just requires at least Java 6.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
On older Android either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
Java 8+ APIs available through desugaring
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
If we don't want to add the ThreeTenABP library to our project, we need to normalize to a date-without-time in UTC, in order to prevent things like Daylight Savings Time to skew the results.
For that, a helper method is appropriate:
static long toDateUtcMillis(Date time) {
// Get year/month/day according to default time zone
Calendar cal = Calendar.getInstance();
cal.setTime(time);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
// Set year/month/day in UTC
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.clear();
cal.set(year, month, day);
return cal.getTimeInMillis();
}
We can now easily calculate the number of days. In the following we return negative value if the dates are reverse. Add call to Math.abs() if that's not desired.
static int daysBetween(Date date1, Date date2) {
long dateMillis1 = toDateUtcMillis(date1);
long dateMillis2 = toDateUtcMillis(date2);
return (int) TimeUnit.MILLISECONDS.toDays(dateMillis2 - dateMillis1);
}
Test
public static void main(String[] args) throws Exception {
test("3 September 2020 14:35", "4 September 2020 00:35");
test("3 September 2020 23:55", "4 September 2020 00:35");
test("2 September 2020 02:23", "4 September 2020 00:35");
}
static void test(String date1, String date2) throws ParseException {
// Parse the date strings in default time zone
SimpleDateFormat format = new SimpleDateFormat("d MMMM yyyy HH:mm", Locale.US);
int days = daysBetween(format.parse(date1), format.parse(date2));
System.out.println("Timestamp is: " + date1);
System.out.println("Compared to: " + date2);
System.out.println("Days difference = " + days);
System.out.println();
}
Output
Timestamp is: 3 September 2020 14:35
Compared to: 4 September 2020 00:35
Days difference = 1
Timestamp is: 3 September 2020 23:55
Compared to: 4 September 2020 00:35
Days difference = 1
Timestamp is: 2 September 2020 02:23
Compared to: 4 September 2020 00:35
Days difference = 2
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.
I've found an issue about Java DST change.
When the exact moment that DST(Daylight Saving time) happened, the certain time may appears two times.
For example, for the timezone America/Sao_Paulo, it changed backward one hour from Sunday, 21 February 2016, 00:00:00 to Saturday, 20 February 2016, 23:00:00.
The time 23:00 appears twice that day.
In my use case, I'd like to take the second time. For example, when I want the data of this day, I actually mean this day completely (even if maybe it contains 25 hours).
But in my opinion, it seems that Java take the first time of this moment.
Example:
#Test
public void testDSTChange() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
TimeZone tz = TimeZone.getTimeZone("America/Sao_Paulo");
long dateInput = sdf.parse("2016-02-20T23:00:00").getTime();
long dateUTC = dateInput - tz.getOffset(dateInput);
long dateInput2 = sdf.parse("2016-02-21T02:00:00").getTime();
long dateUTC2 = dateInput2 - tz.getOffset(dateInput2);
System.out.println("Difference: " + (dateUTC2 - dateUTC) / 3600 / 1000);
}
In this example, it shows that the difference between these two dates is 4 hours, not the 3 hours that I expected. So I wonder that if there's any other way to do it ?
Well, the time at the DST change will occur twice when the DST changes backward, there is no way around it. There may be another way around, but let me make a suggestion, internally use UTC time (GMT) it has no DST internally, convert to local time (wall clock time) on output for display purposes only. You will never have to worry about it again.
In my use case, I'd like to take the second time. For example, when I want the data of this day, I actually mean this day completely (even if maybe it contains 25 hours).
From this statement, I'll assume you are actually doing some kind of range query to gather data over the entire day. The best way to approach this is to query from the start of the day inclusive, to the start of the next day exclusive. In other words: startOfDay <= dataPoint(s) < startOfNextDay
This works well in your scenario, because the clock doesn't actually hit 00:00 until the next day has started. It ticks like this:
23:58
23:59
23:00
23:01
...
23:58
23:59
00:00 <--- start of next day
So use half-open intervals, and it should just work out.
Do be careful in the spring transition though, when the start of the day is 01:00, not 00:00. :)
Also, Jon's comment about using Java 8's new time APIs, or Joda-Time are spot-on. They both offer you better control over this situation. Older Java APIs do not.
LocalDateTime l1 = LocalDateTime.of(2016, Month.FEBRUARY, 20, 23, 0, 0);
ZoneId id = ZoneId.of("America/Sao_Paulo");
ZoneOffset offset = ZoneOffset.of("-08:00");
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(l1, offset, id);
long el1= zonedDateTime.toEpochSecond();
LocalDateTime l2 = LocalDateTime.of(2016, Month.FEBRUARY, 21, 2, 0, 0);
ZonedDateTime zonedDateTime1 = ZonedDateTime.ofInstant(l2, offset, id);;
long el2= zonedDateTime1.toEpochSecond();
System.out.println("Difference: " + (el2-el1 ) / 3600 );
====================================
on console it prints
Difference: 3
How can I get the current local wall clock time (in number of millis since 1 Jan 1970) in London? Since my application can run on a server in any location, I think I need to use a TimeZone of "Europe/London". I also need to take Daylight Savings into account i.e. the application should add an hour during the "summer".
I would prefer to use the standard java.util libraries.
Is this correct?
TimeZone tz = TimeZone.getTimeZone("Europe/London") ;
Calendar cal = Calendar.getInstance(tz);
return cal.getTime().getTime() + tz.getDSTSavings();
Thanks
I'm not sure what this quantity represents, since the "number of millis since 1 Jan 1970" doesn't vary based on location or daylight saving. But, perhaps this calculation is useful to you:
TimeZone london = TimeZone.getTimeZone("Europe/London");
long now = System.currentTimeMillis();
return now + london.getOffset(now);
Most applications are better served using either UTC time or local time; this is really neither. You can get the UTC time and time in a particular zone like this:
Instant now = Instant.now(); /* UTC time */
ZonedDateTime local = now.atZone(ZoneId.of("Europe/London"));
Others have said that it may well not be a good idea to do this - I believe it depends on your situation, but using UTC is certainly something to consider.
However, I think you've missed something here: the number of seconds which have occurred since January 1st 1970 UTC (which is how the Unix epoch is always defined - and is actually the same as in London, as the offset on that date was 0) is obtainable with any of these expressions:
System.currentTimeMillis()
new Date().getTime()
Calendar.getInstance().getTime().getTime()
If you think about it, the number of milliseconds since that particular instant doesn't change depending on which time zone you're in.
Oh, and the normal suggestion - for a much better date and time API, see Joda Time.
To get the current time in London:
SimpleDateFormat f = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z");
f.setTimeZone(TimeZone.getTimeZone("Europe/London"));
System.out.println(f.format(GregorianCalendar.getInstance().getTime()));