Check whether a given Instant fits a defined Period - java

What we get is an Instant and a "date-grid" defined by a period (which defines the interval of datapoints, e.g.: Every Month, Every 3 Months, etc.) and a start date where we started that grid.
private Instant getValidDate(Instant request, Instant start, Period period) {
if(isOnGrid(request, start, period)) {
return request;
}
else {
return getNextPriorDateOnGrid(request, start, period);
}
}
An example:
Given are the following parameters:
request = Instant("2000-05-02T07:42:00.000Z") //Second May of 2000 7:42 AM
start = Instant("2000-01-01T06:00:00.000Z") //First January of 2000 6:00 AM
period = Period("3M") //Every 3 Months
isOnGrid(request, start, period); //Should return false
getNextPriorDate(request, start, period) //Should return the First April of 2000 6:00 AM
I really have no idea how to get this with reasonable performance (its a critical place in code)
How do you check whether a distant future date (given by the Instant) is exactly on this grid, and if not, what is the next past date that was on the grid?
EDIT: I forgot to mention: All times and dates are assumed to be in UTC Timezone

Here is a simple test case that should match your requirements:
package test;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
public class Java8PeriodAndInstant2 {
public static void main(String[] args) {
// LocalDate request=LocalDate.of(2000, 5, 2);
// LocalDate start=LocalDate.of(2000, 1, 1);
LocalDateTime start = Instant.parse("2000-01-01T06:00:00.000Z").atZone(ZoneId.of("UTC")).toLocalDateTime();
LocalDateTime request = Instant.parse("2000-05-02T07:42:00.000Z").atZone(ZoneId.of("UTC")).toLocalDateTime();
Period period = Period.ofMonths(3);
System.out.println("is on grid " + isOnGrid(request, start, period));
System.out.println("is on grid " + isOnGrid(LocalDateTime.of(2000, 4, 2,0,0), start, period));
System.out.println("is on grid " + isOnGrid(LocalDateTime.of(2000, 4, 1,0,0), start, period));
System.out.println("getNextPriorDate " + getNextPriorDate(request, start, period));
System.out.println("isOnGrid " + isOnGrid(Instant.parse("2000-01-03T05:00:00.000Z").atZone(ZoneId.of("UTC")).toLocalDateTime(), start, Period.ofDays(1)));
System.out.println("isOnGrid " + isOnGrid(Instant.parse("2000-01-03T06:00:00.000Z").atZone(ZoneId.of("UTC")).toLocalDateTime(), start, Period.ofDays(1)));
System.out.println("getNextPriorDate " + getNextPriorDate(Instant.parse("2000-01-03T05:00:00.000Z").atZone(ZoneId.of("UTC")).toLocalDateTime(), start, Period.ofDays(1)));
}
private static boolean isOnGrid(LocalDateTime start, LocalDateTime request, Period period) {
if (period.getDays() != 0) {
return ((Duration.between(start, request).toHours()%period.getDays())==0);
}
Period diffPeriod = Period.between(start.toLocalDate(), request.toLocalDate());
if (diffPeriod.getDays()!=0) {
return false;
}
if (period.getMonths() != 0) {
return ((diffPeriod.toTotalMonths()) % (period.toTotalMonths()) == 0);
}
if (diffPeriod.getMonths()!=0) {
return false;
}
if (period.getYears() != 0) {
return ((diffPeriod.getYears()) % (period.getYears()) == 0);
}
return false;
}
private static LocalDateTime getNextPriorDate(LocalDateTime request, LocalDateTime start, Period period) {
if (period.getDays() != 0) {
long hoursDiff=Duration.between(start, request).toHours();
return start.plusDays(hoursDiff/24);
}
Period diffPeriod = Period.between(start.toLocalDate(), request.toLocalDate());
if (period.getMonths() != 0) {
diffPeriod = diffPeriod.withDays(0);
long monthDiff = diffPeriod.toTotalMonths() % period.toTotalMonths();
return start.plus(diffPeriod).minusMonths(monthDiff);
}
if (period.getYears() != 0) {
diffPeriod = diffPeriod.withDays(0);
diffPeriod.withMonths(0);
long yearsDiff = diffPeriod.getYears() % period.getYears();
return start.plus(diffPeriod).minusYears(yearsDiff);
}
return null;
}
}
it works with periods of days or months or years.

You cannot add Periods to Instants. They have a different "scope".
An Instant i simply represents a point in the timeline, counting the amount of millis/nanos from a specific point in time called "Epoch".
At this instant i, the time at the clock at the wall (even the date in a calendar) differs around the world. It depends on the timezone you are in.
A Period respects different lengths of its representation among different timezones starting at differnt dates. For example: A month lasts 30 days in June but 31 days in August. And it is even more complex if daylight saving shifts occur.
An Instant has no idea, what a "month" actually is. You can parse it from a String and output it to it, but internally it does not represent a human understandable form of a month like 'Jan', 'Feb', ... .
This is, why you have to align an Instant to a LocalDateTime or ZonedDateTime using a ZoneId or an ZoneOffset. Theses classes understand and can work with Periods.
The following code converts your Instants to LocalDateTimes to take into account the above comments:
private static Instant getValidDate2(Instant request, Instant start, Period period)
{
assert(!request.isBefore(start));
// multiplication of period only works with days exclusive or
// zero daypart of period
assert(period.getDays() == 0 || (period.getMonths() == 0 && period.getYears() == 0));
ZoneId utcZone = ZoneOffset.UTC;
LocalDateTime ldstart = LocalDateTime.ofInstant(start, utcZone);
LocalDateTime ldreq = LocalDateTime.ofInstant(request, utcZone);
// calculate an approximation of how many periods have to be applied to get near request
Duration simpleDuration = Duration.between(ldstart, ldstart.plus(period));
Duration durationToReq = Duration.between(ldstart, ldreq);
int factor = (int) (durationToReq.toDays() / simpleDuration.toDays()); // rough approximation
// go near to request by a multiple of period
Period jump = Period.of(period.getYears() * factor, period.getMonths() * factor, period.getDays() * factor);
LocalDateTime ldRunning = ldstart.plus(jump);
// make sure ldRunning < request
while (ldRunning.isAfter(ldreq)) {
ldRunning = ldRunning.minus(period);
}
// make sure we pass request and
// save the the last date before or equal to request on the grid
LocalDateTime ldLastbefore = ldRunning;
while (!ldRunning.isAfter(ldreq)) {
ldLastbefore = ldRunning;
ldRunning = ldRunning.plus(period);
}
return ldLastbefore.equals(ldreq) ? request : ldLastbefore.atZone(utcZone).toInstant();
}
Explanation:
To avoid a loop adding period until it gets to request, a rough approximation is done on how often period must be added to start to come to request. A new period being a multiple of the request period is then added and aligned to get the last value of the grid which is less or equal to request. Depending on a comparation between the last value and request, the according instant is returned. In fact, the check is useless besides the fact, that request == request when it was on the grid and not only equal.
Here you can find further informations about java time: https://docs.oracle.com/javase/tutorial/datetime/overview/index.html

Related

How can you check if a given time in milliseconds was yesterday?

Given a time in milliseconds, how can you check if it was yesterday?
You would first convert the millis to a Date or LocalDate and then run the comparison.
Here's a quick example:
import java.time.*;
class DateCheckSample {
public static void main(String[] args) {
// Our input date
long millis = System.currentTimeMillis();
// Convert the millis to a LocalDate
Instant instant = Instant.ofEpochMilli(millis);
LocalDate inputDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
// Grab today's date
LocalDate todaysDate = LocalDate.now();
System.out.println(millis);
// Check if date is yesterday
if (todaysDate.minusDays(1).equals(inputDate)) {
System.out.println(inputDate + " was yesterday!");
} else {
System.out.println(inputDate + " was NOT yeseterday!");
}
}
}
The Result:
2019-02-16 was NOT yesterday!
If you'd like to confirm it's working, just subtract 100000000 from millis before running.
Side Note: As pointed out in the comments on your question, 23:59 is not a millis value...
If you don't want to use Date, you can simply use the modulus operator with a bit of clever arithmetics. System#currentTimeMillis returns the amount of milliseconds that have passed since January 1, 1970, midnight (00:00).
Combining this with the number of milliseconds in a day (86,400,000), we can figure the time at which the last start of day was — which is when today began. Then we can see if the time given to us is smaller or larger than that value.
boolean isToday(long milliseconds) {
long now = System.currentTimeMillis();
long todayStart = now - (now % 86400000);
if(milliseconds >= todayStart) {
return true;
}
return false;
}
To check if a time is yesterday instead of today, we simply check if it is between the start of today and the start of yesterday.
boolean isYesterday(long milliseconds) {
long now = System.currentTimeMillis();
long todayStart = now - (now % 86400000);
long yesterdayStart = todayStart - 86400000;
if(milliseconds >= yesterdayStart && < todayStart) {
return true;
}
return false;
}
You can convert the milliseconds to Date and then compare the day with today's Date.
For reference: Convert millisecond String to Date in Java

Calendar field increment gives unexpected results

I'm working on creating a method to show the difference in time between two dates. To do this, I am using Calendar, and subtracting the dates to receive the remaining number available. However, for some reason, it's returning unexpected results. Here is my method in an extended Date class:
public String getReadableTimeDifference(Date fromDate, boolean showMilliseconds) {
String[] result = new String[7];
Calendar calendar = Calendar.getInstance();
Calendar from = Calendar.getInstance();
calendar.setTime(this);
from.setTime(fromDate);
// The two dates are correct. using a polymorphic method getReadableTimeDifference(boolean),
// it supplies this method with the current date. "this" is always a date in the future.
// Let's say that "this" is a date 10 seconds in the future:
System.out.println(from.getTime());
System.out.println(this);
// Since it's 10 seconds in the future, it will print the same (2016):
System.out.println(calendar.get(Calendar.YEAR) + " " + from.get(Calendar.YEAR));
// It should subtract the from date (2016) from the existing date (2016)
calendar.add(Calendar.YEAR, -from.get(Calendar.YEAR));
calendar.add(Calendar.MONTH, -from.get(Calendar.MONTH));
calendar.add(Calendar.DATE, -from.get(Calendar.DATE));
calendar.add(Calendar.HOUR, -from.get(Calendar.HOUR));
calendar.add(Calendar.MINUTE, -from.get(Calendar.MINUTE));
calendar.add(Calendar.SECOND, -from.get(Calendar.SECOND));
calendar.add(Calendar.MILLISECOND, -from.get(Calendar.MILLISECOND));
// It should print "0" (because 2016-2016 = 0) but instead it prints 2.
System.out.println(calendar.get(Calendar.YEAR));
int years = Math.abs(calendar.get(Calendar.YEAR));
int months = Math.abs(calendar.get(Calendar.MONTH));
int days = Math.abs(calendar.get(Calendar.DATE));
int hours = Math.abs(calendar.get(Calendar.HOUR));
int minutes = Math.abs(calendar.get(Calendar.MINUTE));
int seconds = Math.abs(calendar.get(Calendar.SECOND));
int milliseconds = Math.abs(calendar.get(Calendar.MILLISECOND));
if (years > 0) {
result[0] = Utilities.prettyNumerate("year", years);
}
if (months > 0) {
result[1] = Utilities.prettyNumerate("month", months);
}
if (days > 0) {
result[2] = Utilities.prettyNumerate("day", days);
}
if (hours > 0) {
result[3] = Utilities.prettyNumerate("hour", hours);
}
if (minutes > 0) {
result[4] = Utilities.prettyNumerate("minute", minutes);
}
if (seconds > 0) {
result[5] = Utilities.prettyNumerate("second", seconds);
}
if (milliseconds > 0 && showMilliseconds) {
result[6] = Utilities.prettyNumerate("millisecond", milliseconds);
}
return Utilities.join(Utilities.clean(result), ", ", " and ");
}
prettyNumerate takes a number and appends an "s" to the end of the supplied string if it's over 1, under -1 or 0. clean cleans an array of any null or empty elements. join join's an array by a delimiter, and a final delimiter for the last element. The expected outcome should be:
10 seconds
But instead it's:
2 years, 11 months, 31 days and 10 seconds
Nothing else is being manipulated within this custom date. When I instantiate this custom date, after any custom code is completed I print out this.getTime() and it prints out the correct time in milliseconds, which is 10 seconds into the future as it should be.
The year field in Calendar object of Java is relative to the era. By setting the year to something less or equal to 0 the calendar automatically corrects this by switching the era (from AD to BC or from BC to AD). This behaviour is better known from the other fields. For example, if you set the month to something negative, the year gets decremented accordingly.
Those corrections aren't made individually but rather they are made all at once, usually when you call getTime() to read out the resulting date.
Therefore, if you subtract year 2016 from a date in 2016, it automatically gets corrected to 1st century BC. When you do more subtractions, the time actually reaches 2nd century BC.
As suggested in some comments, you will be better off using Joda time for your usecase.

Creating a new Date from a difference / subtract two periods (having negative time)

I want subtract 2 dates and saving the difference:
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");
Date a = formatter.parse("08:06");
Date b = formatter.parse("08:00");
Date asd = new Date(a.getTime() - b.getTime());
The problem is that asd is not 00:06, but 01:06 (Thu Jan 01 01:06:00 CET 1970), I think that this depend on timezone. But how do I solve this? Should I change the jvm timezone?
Note the javadoc of the Date(long) constructor
Allocates a Date object and initializes it to represent the specified
number of milliseconds since the standard base time known as "the
epoch", namely January 1, 1970, 00:00:00 GMT.
This is not an interval.
Joda-Time does provide an Interval class to perform such calculations. Java 8 should also have such a class when it comes out.
long difference = a.getTime() - b.getTime();
int hours = (int) (difference / 3600000);
int minutes = (int) (difference % 3600000 / 60000);
String formatted = String.format("%02d:%02d", hours, minutes);
Span of Time
Your question is not clear. Apparently you want to work with a span of time. For that you definitely need the Joda-Time library (or the new java.time package built into Java 8).
Joda-Time
Joda-Time offers 3 classes for spans of time:
IntervalRepresents a start-stop pair of points along the timeline. Example: 2013-01-01T00:00:00.000-05:00/2013-01-04T18:00:00.000-05:00
DurationA length of time in milliseconds. Not tied to the timeline.
PeriodRepresents a span as a number of fields, such as years, months, weeks, days, hours, minutes, seconds and millis.
ISO 8601
The ISO 8601 standard defines sensible textual formats to represents various aspects of date-time values. Joda-Time (and java.time in Java 8) use ISO 8601 as its defaults.
In particular, ISO 8601 defines a format for Durations (what Joda-Time calls a Period†). A value is represented as a string in the format of PnYnMnDTnHnMnS. The 'P' indicates the beginning of a duration (Period) string. A 'T' indicates the time portion. Each number precedes its element designator. For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds".
Example Code
// Specify the time zone rather than rely on default. https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Rome" );
DateTime a = new DateTime( 2014, 1, 2, 8, 6, 0, timeZone );
DateTime b = new DateTime( 2014, 1, 2, 8, 0, 0, timeZone );
Period period = new Period( a, b);
long millis = period.toStandardDuration().getMillis();
Dump to console…
System.out.println( "a: " + a );
System.out.println( "b: " + b );
System.out.println( "period: " + period );
System.out.println( "millis: " + millis );
When run…
a: 2014-01-02T08:06:00.000+01:00
b: 2014-01-02T08:00:00.000+01:00
period: PT-6M
millis: -360000
More Info
Search StackOverflow for "joda" and one of those three class names to find many examples.
†Time-related terms vary widely in their usage and meaning. A new standards proposal to normalize such terms has begun. But, for now, get used to having to "translate" the terms when switching contexts.
Thanks for all your solution, it will be useful to my next projects.
But I don't need all the stuff inside Joda, I need just something of lightweight to sum and subtract time (and have negative time), so I take few minutes to create my own class for time handling.
Use this freely:
public class Time {
private int hours;
private int minutes;
private long totalMinutes;
public Time()
{
hours = 0;
minutes = 0;
totalMinutes = 0;
}
public Time(String time) throws RuntimeException
{
try
{
String[] items = time.split(":");
this.hours = Integer.parseInt(items[0]);
this.minutes = Integer.parseInt(items[1]);
this.totalMinutes = hours*60+minutes;
}
catch (RuntimeException re)
{
throw new RuntimeException("Parsing time error", re);
}
}
public Time(Time time)
{
this.hours = time.hours;
this.minutes = time.minutes;
this.totalMinutes = time.totalMinutes;
}
public Time add(Time val)
{
Time ret = new Time(this);
ret.totalMinutes += val.totalMinutes;
ret.hours = (int) Math.abs( (long)ret.totalMinutes/60 );
ret.minutes = (int) Math.abs( (long)ret.totalMinutes%60 );
return ret;
}
public Time sub(Time val)
{
Time ret = new Time(this);
ret.totalMinutes -= val.totalMinutes;
ret.hours = (int) Math.abs( (long)ret.totalMinutes/60 );
ret.minutes = (int) Math.abs( (long)ret.totalMinutes%60 );
return ret;
}
public int getHours() {
return hours;
}
public void setHours(int hours) {
this.hours = hours;
}
public int getMinutes() {
return minutes;
}
public void setMinutes(int minutes) {
this.minutes = minutes;
}
#Override
public String toString()
{
StringBuilder tmpHHmm = new StringBuilder("" + hours);
if (totalMinutes<0)
{
tmpHHmm.insert(0, "-");
}
if (hours<10)
{
tmpHHmm.insert(tmpHHmm.length()-1, "0");
}
tmpHHmm.append(":"+minutes);
if (minutes<10)
{
tmpHHmm.insert(tmpHHmm.length()-1, "0");
}
return tmpHHmm.toString();
}
public long getTotalMinutes() {
return totalMinutes;
}
public void setTotalMinutes(long totalMinutes) {
this.totalMinutes = totalMinutes;
}
}

Determining whether a length of time should be in minutes, hours, days

I have to show the time that has passed after a message has been posted. I'm writing a method the takes publisheddate as input and returns elapsed time.
d1.gettime-pubdate.gettime=time in milliseconds.
I'm writing an if-condition where
if time==something concat days ago,
elseif hours ago
elseif min ago
elseif seconds ago
elseif justnow.
What should the if-condition contain? E.g. days should be if(time>=1440) (if I take the date in minutes, which can be done by time/1000*60*60); what should I do for minutes, hours, &c.?
Based on the advice given here is what i wrote .any improvement suggestions??
public static String TimeElapsed(final Date date)
{
Date Interval=new Date();
DateTime Interval1 = new DateTime();//Converting java time to joda time
Interval1 = new DateTime(date);
DateTime Interval2 = new DateTime();
Interval2=new DateTime(Interval);
//Now we have two joda time instances
Duration dd=new Duration(Interval1,Interval2);
//get duration between given and current time
Duration rd1=new Duration(1000);//seconds
Duration rd2=new Duration(60000);//minutes
Duration rd7=new Duration(60000*2);//2minutes
Duration rd3=new Duration(3600000);//hours
Duration rd6=new Duration(3600000*2);//2hours
Duration rd4=new Duration(86400000);//days
Duration rd5=new Duration(86400000*2);//2days
if(dd.isShorterThan(rd1)){
String k="just now";
return k;
}
else if(dd.isEqual(rd1)){
String k="just now";
return k;
}
else if(dd.isLongerThan(rd1)&& dd.isShorterThan(rd2))
{
String k=dd.getStandardSeconds()+""+" seconds ago";
return k;
}
else if(dd.isEqual(rd2) || dd.isShorterThan(rd7)){
String k=dd.getStandardMinutes()+""+" minute ago";
return k;
}
else if(dd.isLongerThan(rd2) && dd.isShorterThan(rd3)){
String k=dd.getStandardMinutes()+""+" minutes ago";
return k;
}
else if(dd.isEqual(rd3) || dd.isShorterThan(rd6))
{
String k=dd.getStandardHours()+""+" hour ago";
return k;
}
else if(dd.isLongerThan(rd3) && dd.isShorterThan(rd4)){
String k=dd.getStandardHours()+""+"hours ago";
return k;
}
else if(dd.isEqual(rd4) || dd.isShorterThan(rd5) ) {
String k=dd.getStandardDays()+""+" day ago";
return k;
}
else if(dd.isLongerThan(rd4)){
String k=dd.getStandardDays()+""+" days ago";
return k;
}
else{
String k="";
return k;
}
}
I agree with wrschneider99's suggestion of using Joda-Time, but IMO you should be using a Duration rather than an Interval, assuming you're talking about an elapsed duration which is a fixed number of milliseconds, rather than something more human-centric such as a number of months.
I would suggest something like:
// Assuming this really is a constant...
private static final Duration POST_DURATION = Duration.standardDays(2);
// Then elsewhere...
Instant expiry = post.getTime().plus(POST_DURATION);
// See below
Instant now = clock.now();
if (now.isAfter(expiry)) {
...
}
Note that I'd use some sort of Clock interface with a "just for test" implementation and a "based on system time" implementation and inject that as you would any other dependency, for testability. See Kevin Bourrillion's blog post about pure functions for more on this.
I think you're looking for something like Joda Time's Period.normalizedStandard method.
See:
Period to string

How to compare two Joda time Periods

It does not seem straighforward.
I am trying this:
#Override
public int compare(Period o1, Period o2) {
return o1.toStandardDays().getDays() > o2.toStandardDays().getDays() ? -1 : (o1.toStandardDays().getDays() == o2.toStandardDays().getDays() ? 0 : 1);
}
But I get this exception:
java.lang.UnsupportedOperationException: Cannot convert to Days as this period contains months and months vary in length
at org.joda.time.Period.checkYearsAndMonths(Period.java:1455)
at org.joda.time.Period.toStandardDays(Period.java:1314)
I hoped Peroid would have an isLongerThan(Period p) method.
From the Joda Documentation:
To compare the actual duration of two periods, convert both to durations using toDuration, an operation that emphasises that the result may differ according to the date you choose.
The two toDuration methods are BasePeriod#toDurationTo(ReadableInstant) and BasePeriod#toDurationFrom(ReadableInstant). This means that you must choose either a start or end instant of this period in order to be able to compute its duration.
If that is a problem for you, then you might want to directly use Duration instead of Period.
As Matt Ball also explained in his answer, to compare 2 periods you need to convert them to a duration first. Durations are relative to a certain point in time, so you need to do something like this:
public static boolean isLonger(Period p1, Period p2) {
Instant now = Instant.now();
Duration d1 = p1.toDurationTo(now);
Duration d2 = p2.toDurationTo(now);
return d1.isLongerThan(d2);
}
public static boolean isShorter(Period p1, Period p2) {
Instant now = Instant.now();
Duration d1 = p1.toDurationTo(now);
Duration d2 = p2.toDurationTo(now);
return d1.isShorterThan(d2);
}
I wrote a method that should be able to compare two Periods to the nearest day (I didn't care about hours and minutes):
private int comparePeriods(Period period1, Period period2)
{
if (period1.getYears() != period2.getYears())
{
return Integer.compare(period1.getYears(), period2.getYears());
}
if (period1.getMonths() != period2.getMonths())
{
return Integer.compare(period1.getMonths(), period2.getMonths());
}
if (period1.getWeeks() != period2.getWeeks())
{
return Integer.compare(period1.getWeeks(), period2.getWeeks());
}
if (period1.getDays() != period2.getDays())
{
return Integer.compare(period1.getDays(), period2.getDays());
}
return 0;
}
Note that this method expects both periods to be normalised or it will not give accurate results.

Categories