I'm trying to calculate how many minutes are between two dates while excluding periods of time that are arbitrarily defined and occur weekly. I also need to be able to calculate the reverse, where given a time, calculate X number of minutes forward excluding those time periods.
For example, I may have two periods [Fri 5:31pm - Sat 2:26pm] and [Tuesday 3:37am - Thursday 1:14am] that I don't want to count when figuring out the minutes between two dates and when calculating forward.
I currently have code that does this for only one gap, though it's not super efficient and is becoming a strain on my system. I also need to accommodate multiple defined gaps which I currently do not do.
My code which does this for one gap looks like this (hideStart and hideEnter are the start and end DateTime for the gap, absoluteLowValue is the starting time from which I am calculating the distance or time between):
public int absoluteDistance(DateTime high){
long totalMinutes = new Duration(absoluteLowValue,high).getStandardMinutes();
if (!gapHider.isHidingGaps())
return (int)totalMinutes;
int minutesPerWeek = 10080;
long minutesPerHide = new Duration(hideStart, hideEnd).getStandardMinutes();
long numFullWeeks = totalMinutes/minutesPerWeek;
long remainder = totalMinutes%minutesPerWeek;
totalMinutes -= numFullWeeks*minutesPerHide;
DateTime latestEnd = high;
if (latestEnd.getDayOfWeek() == hideEnd.getDayOfWeek() && latestEnd.getSecondOfDay() < hideEnd.getSecondOfDay()){
latestEnd = latestEnd.minusWeeks(1);
}
while (latestEnd.getDayOfWeek() != hideEnd.getDayOfWeek())
latestEnd = latestEnd.minusDays(1);
latestEnd = latestEnd.withTime(hideEnd.getHourOfDay(),
hideEnd.getMinuteOfHour(),
hideEnd.getSecondOfMinute(),
hideEnd.getMillisOfSecond());
DateTime latestStart = high;
if (latestStart.getDayOfWeek() == hideStart.getDayOfWeek() && latestStart.getSecondOfDay() < hideStart.getSecondOfDay()){
latestStart = latestStart.minusWeeks(1);
}
while (latestStart.getDayOfWeek() != hideStart.getDayOfWeek())
latestStart = latestStart.minusDays(1);
latestStart = latestStart.withTime(hideStart.getHourOfDay(),
hideStart.getMinuteOfHour(),
hideStart.getSecondOfMinute(),
hideStart.getMillisOfSecond());
long timeToNearestEnd = new Duration(latestEnd, high).getStandardMinutes();
long timeToNearestStart = new Duration(latestStart, high).getStandardMinutes();
if (timeToNearestEnd < remainder){
totalMinutes -= minutesPerHide;
}else if (timeToNearestStart < remainder){
totalMinutes -= new Duration(latestStart, high).getStandardMinutes();
}
return (int)totalMinutes;
}
public DateTime timeSinceAbsLow(int index){
if (absoluteLowValue != null){
if (!gapHider.isHidingGaps())
return absoluteLowValue.plusMinutes(index);
DateTime date = absoluteLowValue;
long minutesPerWeek = 10080;
long minutesPerHide = new Duration(hideStart, hideEnd).getStandardMinutes();
int difference = (int)(minutesPerWeek - minutesPerHide);
int count = 0;
while (index - count >= difference){
date = date.plusWeeks(1);
count += difference;
}
int remaining = index - count;
DateTime nextStart = date;
while (nextStart.getDayOfWeek() != hideStart.getDayOfWeek())
nextStart = nextStart.plusDays(1);
nextStart = nextStart.withTime(hideStart.getHourOfDay(),
hideStart.getMinuteOfHour(),
hideStart.getSecondOfMinute(),
hideStart.getMillisOfSecond());
long timeDiff = new Duration(date, nextStart).getStandardMinutes();
if (timeDiff < remaining){
date = nextStart.plusMinutes((int)minutesPerHide);
count+= timeDiff;
remaining = index - count;
}
date = date.plusMinutes(remaining);
return date;
}
return new DateTime();
}
Is there a better or easier way of doing this process? I imagine that if I add in the amount of logic to loop through a list of "gaps" that it will just slow it down even more. I'm open to not using Jodatime, I just happen to be using that currently. Any help appreciated!
If I understood correctly, you want to compute the total time between a start and end date, and subtract one ore more periods. So, you could work with Duration objects and then, in the end, convert to whatever you want (minutes, seconds, etc):
// compute all periods you don't want to count
List<Duration> hideList = new ArrayList<>();
// duration between friday and saturday (assuming they have the values of your example)
hideList.add(new Duration(friday, saturday));
// add as many periods you want
// total duration between start and end dates
Duration totalDuration = new Duration(start, end);
// subtract all periods from total
for (Duration duration : hideList) {
totalDuration = totalDuration.minus(duration);
}
// convert to total number of minutes
long totalMinutes = totalDuration.getStandardMinutes();
I'm assuming that all dates used in hideList are between start and end dates (but it's easy to check this using isAfter and isBefore methods).
To do the opposite, you add the totalMinutes to the start date, and then sum all the durations in hideList:
// sum total minutes to start
DateTime dt = start.plusMinutes((int) totalMinutes);
// sum all the periods
for (Duration duration : hideList) {
dt = dt.plus(duration);
}
// dt is the end date
There's one detail: when converting the Duration to a number of minutes, you'll lose the seconds and milliseconds precision, so when doing the opposite algorithm, those fields will be lost. If that's not an issue, just do it as above. But if you want to preserve all the precision, just start by adding the totalDuration object instead of adding the number of minutes:
// sum totalDuratin to start (to preserve seconds and milliseconds precision)
DateTime dt = start.plus(totalDuration);
Related
im trying to work out how i would go about identifying when a certain amount of time(10%) has elapsed since the start date(auctionStart), comparing this with current date and end date(aEnd).
This is the code i have so far, but im quite a ways off figuring this out. Ive been trying to use JodaTime as well.
private Date auctionStart;
private Date aEnd;
public Boolean tenPercentElapsed(){
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String aucStartString = f.format(auctionStart);
Date aucStart = f.parse(aucStartString);
DateTime currentTime = new DateTime();
DateTime startTime = new DateTime(aucStart);
int m = Minutes.minutesBetween(startTime, currentTime).getMinutes();
double minutes = (double)m;
if(){
return true;
}
return false;
}
Transform the following formula into code and you're done:
x = 100 * (current date - start date) / (end date - start date)
Here, x is the percentage completed.
Because you're computing the ratio of two time intervals, the units of the interval don't matter. So you could use dates, minutes, seconds, or even milliseconds in the interval. The last choice ties in well with the return value of java.util.Date#getTime().
Finally if you're using Java 8 and above, then consider ditching Joda time for the new java.time library.
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
is there any way in java to do that? I want it to compute the times like that. 0950-0900 is 50 mins but 1700-1610 = 50 mins instead of 90, 1900-1710 = 110 instead of 190. thanks :)
Have a look at Duration (part of the new Date & Time API introduced in Java SE 8).
Eg. (untested):
long minutes = Duration.between(toLocalTime(1710), toLocalTime(1900)).toMinutes();
private LocalTime toLocalTime(int time){
return LocalTime.of(time / 100, time % 100);
}
You can use the new Java Date API from Java 8.
LocalTime start = LocalTime.parse("19:00");
LocalTime end = LocalTime.parse("17:10");
Duration elapsed = Duration.between(start, end);
System.out.println(elapsed.toMinutes());
This will output: -110 and 110 if you switch start and end.
If you've just got integers, and you don't care about validation, you can do it all without touching time parts at all:
public int getMinutesBetween(int time1, int time2) {
// Extract hours and minutes from compound values, which are base-100,
// effectively.
int hours1 = time1 / 100;
int hours2 = time2 / 100;
int minutes1 = time1 % 100;
int minutes2 = time2 % 100;
// Now we can perform the arithmetic based on 60-minute hours as normal.
return (hours2 - hours1) * 60 + (minutes2 - minutes1);
}
However, I'd strongly recommend that you use more appropriate representations - these aren't just normal int values... they're effectively "time of day" values, so LocalTime (in either Joda Time or Java 8's java.time) is the most appropriate representation, IMO.
I'm wanting to have my JLabel display values in the format of HH:mm:ss without making use of any external libraries. (the label will update every second)
So for example, the following input in seconds and the desired output are below:
Seconds: Output:
--------------------------------------------------
long seconds = 0 00:00:00
long seconds = 5 00:00:05
long seconds = 500 00:08:20
long seconds = 5000 01:23:20
Note: the seconds value is of type long
I'm aware that typically one would just do the following conversions to get the desired numbers:
long s = 5000; //total seconds
long hrs = (s / 3600) //hours
long mins = ((s%3600)/60) //minutes
long secs = (s%60) //seconds
However, this leaves decimals on the values. Perhaps there is some sort of formatting that will allow me to toss the un-needed decimals.
Options I have come across were String.format(), SimpleDateFormat(), or concatenating a string myself.
The thing is, I will be updating this JLabel every second and sometimes it can count to the equivalent of 5-6 days if not longer.
So I'm looking for someone who has more experience in the area than I, and knows the most efficient way to tackle this issue.
I would use SimpleDateFormat if I were you.
If SDF is too slow for you, profile all your options and pick the fastest one, then refactor the rest of your code until it's fast enough.
Remember that premature optimization is the root of all evil, and that you should only really do any optimizing after you've profiled your code and missed your target execution time.
SimpleDateFormat() is really quite appropriate for your needs.
Use the TimeUnit class, as shown here in combination with the javax.swing.Timer class set to execute at 1 second intervals.
If you don't mind values wrapping then use SimpleDateFormat as follows. Remember x1000 to convert to milliseconds and to manually override the timezone.
long value = 5 * 24 * 3600 + 5000;
// wrapping solution
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
// ensure no daylight saving +1 hour
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println(sdf.format(value * 1000));
Output
01:23:20
If you want the hours to go past 23.59.59 then this is the simplest I could come up with. I used DecimalFormat to force at least 2 digits for the hours.
long value = 5 * 24 * 3600 + 5000;
long hours = value / 3600; // whole hours
long mins = value / 60 - hours * 60;
long secs = value % 60;
System.out.println(String.format("%s:%2d:%2d",
new DecimalFormat("00").format(hours), mins, secs));
Output
121:23:20
I've found this to be extremely fast. Try it out. Seconds go from 0 - 59, minutes go from 0 - 59, hours go from 0 - 2,562,047,788,015. Afterwards the hours become negative and begin going towards that maximum.
performing the "+" operator on Strings is very slow. A StringBuilder performs grouping strings together the fastest from what I've seen. You should also be using "chars" not "String/Byte" Bytes are very slow as well. I'd prefer doing only multiplication however dividing by 36 and 6 give decimals that are to large for holding.
StringBuilder sb = new StringBuilder(8);
long hours = time / 3600000;
long minutes = (time - hours * 3600000) / 60000;
long seconds = (time - hours * 3600000 - minutes * 60000) / 1000;
if (hours < 10)
sb.append('0');
sb.append(hours);
sb.append(':');
if (minutes < 10)
sb.append('0');
sb.append(minutes);
sb.append(':');
if (seconds < 10)
sb.append('0');
sb.append(seconds);
String formattedTime = sb.toString();
.....
If you don't want to use a formatter class, you can get your work done by using basic operations like conversion among wrapper classes and String operations. Take a look at this code:
long h, m, s; // Initialize them after calculation.
String h1, m1, s1;
h1 = Long.toString( h );
m1 = Long.toString( m );
s1 = Long.toString( s );
if ( s1.length() < 2 )
s1 = "0" + s1;
if ( m1.length() < 2 )
m1 = "0" + m1;
if ( h1.length() < 2 )
h1 = "0" + h1;
String output = h1+":"+m1+":"+s1;
Supposing you have correctly calculated values of seconds, minutes and hours, you can gather String versions of these variables, then format them with a simple length check and finally concatenate these time unit parts.
i think you want to do the math you indicated, but take the floor of each value. then concatenate..
public class Test{
public static void main(String args[]){
double d = -100.675;
float f = -90;
System.out.println(Math.floor(d));
System.out.println(Math.floor(f));
System.out.println(Math.ceil(d));
System.out.println(Math.ceil(f));
}
}
I've implemented a stopwatch that works fine without considering that bank holidays and weekends shouldn't be counted in the total duration. I was looking for some open-source library where I could get the elapsed time, passing a start instant, end instant and a set of bank holidays (weekends aren't counted in). The only library that makes me things easier is net.sf.jtemporal, but I have still to amplify the functionality.
Could anyone tell me if there is some useful library to get the wanted functionality?
As I have mentioned there, probably the best and easiest approach is to create a table containing information about each day (work day count from beginning / bank holiday, etc; one row per day = 365 rows per year) and then just use count function / with proper selection.
I doubt you can find something that specific. But it's easy enough to create your own logic. Here's some pseudocode...
private long CalculateTimeSpan(DateTime BeginDate, DateTime EndDate, ArrayList<DateTime> BankHollidays)
{
long ticks = 0;
while (BeginDate <= EndDate) // iterate until reaching end
{
if ((BeginDate is holliday?) || (BeginDate is Weekend?))
skip;
else
ticks += (24*60*60*1000);
BeginDate = BeginDate + 1 day; // add one day and iterate
}
return ticks;
}
Do you only count Bank Hours too? 9AM - 3PM? Or is it 24 hours a day?
You should take a look at Joda Time. It is a much better date/time API than the one included with Java
I think this would be a valid solution to what your are looking for. It calculates the elapsed time (considering that one working day has 24 hours) without count the bank holidays and weekends in:
/**
* Calculate elapsed time in milliseconds
*
* #param startTime
* #param endTime
* #return elapsed time in milliseconds
*/
protected long calculateElapsedTimeAux(long startTime, long endTime) {
CustomizedGregorianCalendar calStartTime = new CustomizedGregorianCalendar(this.getTimeZone());
CustomizedGregorianCalendar calEndTime = new CustomizedGregorianCalendar(this.getTimeZone());
calStartTime.setTimeInMillis(startTime);
calEndTime.setTimeInMillis(endTime);
long ticks = 0;
while (calStartTime.before(calEndTime)) { // iterate until reaching end
ticks = ticks + increaseElapsedTime(calStartTime, calEndTime);
}
return ticks;
}
private long increaseElapsedTime(CustomizedGregorianCalendar calStartTime, CustomizedGregorianCalendar calEndTime) {
long interval;
long ticks = 0;
interval = HOURS_PER_DAY*MINUTES_PER_HOUR*SECONDS_PER_MIN*MILLISECONDS_PER_SEC; // Interval of one day
if ( calEndTime.getTimeInMillis() - calStartTime.getTimeInMillis() < interval) {
interval = calEndTime.getTimeInMillis() - calStartTime.getTimeInMillis();
}
ticks = increaseElapsedTimeAux(calStartTime, calEndTime, interval);
calStartTime.setTimeInMillis(calStartTime.getTimeInMillis() + interval);
return ticks;
}
protected long increaseElapsedTimeAux(CustomizedGregorianCalendar calStartTime, CustomizedGregorianCalendar calEndTime, long interval) {
long ticks = 0;
CustomizedGregorianCalendar calNextStartTime = new CustomizedGregorianCalendar(this.getTimeZone());
calNextStartTime.setTimeInMillis(calStartTime.getTimeInMillis() + interval);
if ( (calStartTime.isWorkingDay(_nonWorkingDays) && calNextStartTime.isWorkingDay(_nonWorkingDays)) ) { // calStartTime and calNextStartTime are working days
ticks = interval;
}
else {
if (calStartTime.isWorkingDay(_nonWorkingDays)) { // calStartTime is a working day and calNextStartTime is a non-working day
ticks = (calStartTime.getNextDay().getTimeInMillis() - calStartTime.getTimeInMillis());
}
else {
if (calNextStartTime.isWorkingDay(_nonWorkingDays)) { // calStartTime is a non-working day and calNextStartTime is a working day
ticks = (calNextStartTime.getTimeInMillis() - calStartTime.getNextDay().getTimeInMillis());
}
else {} // calStartTime and calEndTime are non-working days
}
}
return ticks;
}