I was trying to find the difference in time (HH:mm:ss.SSS) in Java, where each time can be more than 24 hours. SimpleDateFormat does not support time which is greater than 24 hours.
For example,
Time A = 36:00:00.00
Time B = 23:00:00.00
I would like to get the answer of 13:00:00.00. (13 hours).
Does any one know whether there are any Java libraries that can perform the subtraction. Also would like to know whether time addition is possible with the Java library.
You don't need a third party library
This is simple math, and doesn't directly appear to have anything to do with Date DateTime or Timestamp instances, but does appear to be interval related, and there is built in functionality into the JDK >= 1.5 with java.util.concurrent.TimeUnit to handle just this type of math without introducing any dependencies.
Here is the code to parse your input and convert it into milliseconds, which you can then convert back into whatever String format you want, I conveniently chose the format you requested.
java.util.concurrent.TimeUnit is a little hidden gem that most people don't know about that kind of snuck in to 1.5. It is kind of criminal that this class is buried in the java.util.concurrent package and no one seems to know about it.
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main
{
private static long parseInterval(final String s)
{
final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})$");
final Matcher m = p.matcher(s);
if (m.matches())
{
final long hr = Long.parseLong(m.group(1)) * TimeUnit.HOURS.toMillis(1);
final long min = Long.parseLong(m.group(2)) * TimeUnit.MINUTES.toMillis(1);
final long sec = Long.parseLong(m.group(3)) * TimeUnit.SECONDS.toMillis(1);
final long ms = Long.parseLong(m.group(4));
return hr + min + sec + ms;
}
else
{
throw new IllegalArgumentException(s + " is not a supported interval format!");
}
}
private static String formatInterval(final long l)
{
final long hr = TimeUnit.MILLISECONDS.toHours(l);
final long min = TimeUnit.MILLISECONDS.toMinutes(l - TimeUnit.HOURS.toMillis(hr));
final long sec = TimeUnit.MILLISECONDS.toSeconds(l - TimeUnit.HOURS.toMillis(hr) - TimeUnit.MINUTES.toMillis(min));
final long ms = TimeUnit.MILLISECONDS.toMillis(l - TimeUnit.HOURS.toMillis(hr) - TimeUnit.MINUTES.toMillis(min) - TimeUnit.SECONDS.toMillis(sec));
return String.format("%02d:%02d:%02d.%03d", hr, min, sec, ms);
}
public static void main(final String[] args)
{
final String s1 = "36:00:00.000";
final String s2 = "23:00:00.000";
final long i1 = parseInterval(s1);
final long i2 = parseInterval(s2);
System.out.println(formatInterval(i1 - i2));
}
}
the output is
13:00:00.000
I assume you meant for the last number to be milliseconds which should have a resolution of 3 digits.
Here's a complete solution using JodaTime. I dare say there's no comparably easy and elegant way to do it using java.util or java.util.concurrent apis:
public static int getHoursBetween(final String date1, final String date2){
final DateTimeFormatter fmt =
DateTimeFormat
.forPattern("HH:mm:ss.SS")
.withChronology(
LenientChronology.getInstance(
GregorianChronology.getInstance()));
return Hours.hoursBetween(
fmt.parseDateTime(date1),
fmt.parseDateTime(date2)
).getHours();
}
(LenientChronology makes sure that values like 38:00:00.00 are supported)
Take a look at Joda-Time, which is a complete library for dealing with times and dates in Java, including arithmetic on intervals.
Try converting time to seconds, subtract then convert back to simple time format.
Here is a sample of how to get the difference between two Dates using Joda-Time (which was mentioned by Jim Garrison earlier. It really is one of the best Time libraries available.
public static void main(String[] args) {
DateTime timeA = new DateTime(2011, 6, 13, 12, 0, 0, 0);
DateTime timeB = new DateTime(2011, 6, 12, 23, 0, 0, 0);
Period period = new Period(timeB, timeA);
System.out.println(period.getHours());
}
Related
I am implementing a service (not going to production anywhere anytime) which should receive a LocalDateTime and a Duration and should check if given time is between company working hours (which are 8:00-22:00), the working hours should be (somehow) configurable:
lets say that I have a:
public class CompanyWorkingHoursService {
private static final Int OPENING_HOUR = 8;
private static final Int CLOSING_HOUR = 22;
private boolean isMeetingBetweenWorkingHours(LocalDateTime beginningDateTime, Duration duration) {
LocalDateTime endingDateTime = beginningDateTime.plus(duration);
}
and I'm stuck.
I can change the type of OPENING_HOUR and CLOSING_HOUR to whatever I want. I can get hours and minutes from LocalDateTime but those are integers. And I don't want to compare whole dates - i need just hours and minutes.
I have found some solutions using java.util.Date but I would like to stay with LocalDateTime if possible...
The "best" thing is to avoid integers. So define the opening and closing hours as LocalTime, and compare the dates using the isAfter(), isBefore() and equals() provided by LocalTime:
private static final LocalTime OPENING_HOUR = LocalTime.of(8, 0);
private static final LocalTime CLOSING_HOUR = LocalTime.of(22, 0);
private boolean isMeetingBetweenWorkingHours(LocalDateTime beginningDateTime, Duration duration) {
LocalDateTime endingDateTime = beginningDateTime.plus(duration);
return !beginningDateTime.toLocalTime().isBefore(OPENING_HOUR)
&& !endingDateTime.toLocalTime().isAfter(CLOSING_HOUR));
}
If the working hours should be (somehow) configurable, you could pass them to the method, too. Afterwards create LocalDateTime instances from those values in combination with the date of the meeting.
Maybe like this:
public static boolean isMeetingBetweenWorkingHours(
LocalDateTime startMeeting, Duration meetingDuration,
int openFrom, int openUntil) { // pass start and end hour of day
/*
* create the working time hours using the hours of day passed
* and using the date of the meeting start passed
*/
LocalDateTime startWorkingHours = LocalDateTime.of(startMeeting.toLocalDate(),
LocalTime.of(openFrom, 0));
LocalDateTime endWorkingHours = LocalDateTime.of(startMeeting.toLocalDate(),
LocalTime.of(openUntil, 0));
// calculate the end time of the meeting
LocalDateTime endMeeting = startMeeting.plus(meetingDuration);
// then return if the meeting fully fits into the working time slot
return !startMeeting.isBefore(startWorkingHours)
&& !endMeeting.isAfter(endWorkingHours);
}
I've included the whole method below, but really the challenge is simulating DateTime.MaxValue.Ticks in Java 8. I also don't know the equivalent of ".ToString("D19") in Java.
I thought I had figured out how to begin, which was by using Instant.MAX.toEpochMilli(), which I could then multiply by 10000 to get Ticks. Sadly, this simple statement throws an exception, so it's a non-starter:
Caught: java.lang.ArithmeticException: long overflow
Here is the original method. It's used to query Azure Storage Tables for historical metrics.
// Creates a TableQuery for getting metrics by timestamp
private static TableQuery GenerateMetricTimestampQuery(string partitionKey, DateTime startTime, DateTime endTime)
{
return GenerateMetricQuery(
partitionKey,
(DateTime.MaxValue.Ticks - endTime.Ticks + 1).ToString("D19") + "__",
(DateTime.MaxValue.Ticks - startTime.Ticks).ToString("D19") + "__");
}
Here is an example of a RowKey field value:
2519303419199999999__
I've spent a day on this and I'm pretty stumped. Any help would be greatly appreciated.
If possible, I would prefer to do this without JodaTime.
UPDATE1*** Based on a comment, here is an example of the exception in Java.
import java.time.Instant;
public class Tester {
public static void main(String[] args){
System.out.println(Instant.MAX.toEpochMilli());
}
}
UPDATE Original answer didn't account for offset difference between Java epoch (1970) and .NET ticks (0001). Corrected!
For reference, Long.MAX_VALUE (Java) is:
9,223,372,036,854,775,807
In .NET, DateTime.MaxValue is:
9999-12-31 23:59:59.9999999
3,155,378,975,999,999,999 ticks1 (~ 1/3 of long)
In Java 8, Instant.MAX is:
+1000000000-12-31 23:59:59.999999999
31,556,889,864,403,199,999,999,999 nanos (overflows long)
315,568,898,644,031,999,999,999 ticks2 (overflows long)
31,556,889,864,403,199,999 millis (overflows long)
31,556,889,864,403,199 seconds (~ 1/292 of long)
For reference, your value of 2519303419199999999 is:
2016-08-23 13:28:00
636,075,556,800,000,000 ticks1 (~ 1/14 of long)
14,719,588,800,000,000 ticks2 (~ 1/626 of long)
1) Since 0001-01-01 (.NET) 2) Since 1970-01-01 (Java)
As you can see, Instant.MAX in "ticks" will not fit in a long. Not even milliseconds will fit.
More importantly Instant.MAX is not the same value as DateTime.MaxValue.
I would suggest you just create a constant for the value, e.g.
public static final long DATETIME_MAXVALUE_TICKS = 3155378975999999999L; // .NET: DateTime.MaxValue.Ticks
That way you'll get same string values as you .NET code:
public static final long EPOCH_OFFSET = 62135596800L; // -Instant.parse("0001-01-01T00:00:00Z").getEpochSecond()
private static long getTicks(Instant instant) {
long seconds = Math.addExact(instant.getEpochSecond(), EPOCH_OFFSET);
long ticks = Math.multiplyExact(seconds, 10_000_000L);
return Math.addExact(ticks, instant.getNano() / 100);
}
public static void main(String[] args) {
Instant startTime = Instant.parse("2016-08-23T13:28:00Z");
String s = String.format("%19d", DATETIME_MAXVALUE_TICKS - getTicks(startTime));
System.out.println(s);
}
Output:
2519303419199999999
long maxSeconds = Instant.MAX.getEpochSecond(); //31556889864403199
int maxNanos = Instant.MAX.getNano(); //999999999
These two values can be used together to create a precise MAX value as number:
31556889864403199,999999999
If you need to print it you need to join them as String.
You can also create a BigDecimal from these two values like:
BigDecimal max = new BigDecimal(Long.toString(maxSeconds) + "." + String.format("%09d", maxNanos));
And operate on it:
BigDecimal now = new BigDecimal(String.format("%d.%09d", Instant.now().getEpochSecond(), Instant.now().getNano()));
System.out.println(max.subtract(now).toString());
I am having some strange output when I am attempting to get an average from a long value and converting it to a String (in HH:mm:ss) format. I'm using the Joda time library, which mostly has been a life saver.
Here's what I have so far:
//this is in a static class
public static Duration calculateTime(Date start, Date end) {
Duration duration = new Duration(start.getTime(), end.getTime());
return duration;
}
public static String convertMillisToTime(long milliseconds) {
return String.format("%02d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(milliseconds),
TimeUnit.MILLISECONDS.toMinutes(milliseconds) -
TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(milliseconds)),
TimeUnit.MILLISECONDS.toSeconds(milliseconds) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(milliseconds)));
}
long averageRunTime = 0;
averageRunTime += calculateTime(drs.startTime, drs.endTime).getMillis();
Since this is done in an array list, to get the average, I am taking
averageRunTime = averageRunTime / (long)arrayList.size();
when I get to the point of
convertMillisToTime(averageRunTime);
It's returning a value that is higher than any times I have. IE( I have no job times that are over 11 hours) but this is returning me a String with 11:mm:ss. Not quite sure WHY?
I used the convertMillisToTime from: How to convert milliseconds to "hh:mm:ss" format?
Can anyone point me in the right direction?
Thank you in advance,
RR
Edit: I've modified the code that converts the long to time using TimeUnit. It's strange though, I grabbed three runs (time streams) and got 09:14:17, 08:57:18, 09:10:25 for the length of each run, and the average is coming out as: 10:27:26.
You could just use a formatter from Joda Time instead, for example...
public static final PeriodFormatter PERIOD_FORMATTER = new PeriodFormatterBuilder().printZeroAlways().
appendHours().appendSeparator(":").
appendMinutes().appendSeparator(":").
appendSeconds().appendSeparator(".").
appendMillis().toFormatter();
public static String toString(Duration duration) {
return duration == null ? "[invalid duration]" : duration.toPeriod().toString(PERIOD_FORMATTER);
}
Class level variables:
static int secondsInterval = 100000000;
static long secondsSpecial=0;
static Date dateSecondsReg;
I have the following function I pass two arguments to this function:
The number of seconds which have elapsed since a certain date.
The actual date (Beginning date) since when the calculation commences.
the method is:
public static void secondsToNotify(long seconds, String d){
Date dt = Convert(d);
System.out.println(""+dt);
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
secondsSpecial = secondsInterval*(1+(seconds/secondsInterval));
cal.add(Calendar.SECOND, (int) secondsSpecial);
dateSecondsReg = cal.getTime();
System.out.println(""+dateSecondsReg);
System.out.println(""+secondsSpecial);
}
I pass the following arguments to the method:
secondsToNotify(2682810000L, "1929-01-02");
To my amazement the resulting date, instead of being in future is in past:
Results
1929-01-02
Mon Jun 17 17:08:24 IST 1878
2700000000
Cant understand where I am going wrong, any hints?
seconds / secondsInterval will be carried out in integer arithmetic which means that any remainder is lost.
Pre-multiplying by 1.0 will force the calculation to be carried out in floating point:
1.0 * seconds / secondsInterval
(P.S. Eventually, your cast (int)secondsSpecial will give you trouble if secondsSpecial gets large: you need to rethink that. One idea would be to drop all your int types. Then, as a workaround to Calendar.add having an int as the interval argument, convert secondsSpecial to years and seconds and add the intervals successively.)
you have an integer overflow here
Cal.add(Calendar.SECOND, (int) secondsSpecial);
that cast to int results in -1594967296 being passed
Check System.out.println(Integer.MAX_VALUE); will give 2147483647 ie 2^31 -1
since it seems that Calendar.add only takes in int as increment how about
cal.setTimeInMillis(dt.getTime() + secondsSpecial)
I think the problem lies in:
Cal.add(Calendar.SECOND, (int) secondsSpecial);
You're casting a long to int, and unless I've miscounted a zero or two I think your long is bigger than Integer.MAX_VALUE. So this will wrap around to some negative number, and there's your subtraction.
(For the record, starting variable names with capital letters is very bad style in Java, and made your code quite hard to read.)
For anyone who stumbles here, this is what I came up with as of now ( Not too robust though, improving it)
public class testSplSec {
static int secondsInterval = 100000000;
static long secondsSpecial=0;
static Date dateSecondsReg;
static Integer intValue;
public static void secondsToNotify(long seconds, String d){
Date dt = Convert(d);
System.out.println(""+dt);
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
secondsSpecial = secondsInterval*(1+(seconds/secondsInterval));
System.out.println(""+(secondsSpecial));
long hour = (secondsSpecial /60)/60;
System.out.println(""+hour);
if(hour >= Integer.MAX_VALUE){
}else{
intValue = (int) (long)hour;
}
cal.add(Calendar.HOUR,intValue);
dateSecondsReg = cal.getTime();
System.out.println(""+dateSecondsReg);
System.out.println(""+secondsSpecial);
}
public static Date Convert(String S){
String DateStr = S;
Date d = null;
try {
d = new SimpleDateFormat("yyyy-MM-dd").parse(DateStr);
} catch (ParseException e) {
e.printStackTrace();
}
java.sql.Date d1 = new java.sql.Date(d.getTime());
return d1;
}
public static void main(String[] args){
secondsToNotify(2682810000L, "1929-01-02");
}
}
I need a good OLEDate java implementation, and this one does not seem to be working. Is there any known good opensource implementations (like in apache commons)? If not, where do I read about it, so that I write my own implementation?
Try this code:
public static Date getDateFromCOMDate(float comtime) {
String floatstr = String.valueOf(comtime);
String [] ss = floatstr.split("\\.");
long nulltime = -2209183200000L;
long dayms = 86400000;
int days = Integer.valueOf(ss[0]);
float prop = comtime - days;
long cdayms = Math.round(dayms * prop);
long time = nulltime + days*dayms + cdayms;
Date d = new Date(time);
return d;
}
The previous implementation is buggy, e.g. wrong dates for 1.0 and -1.25.
The implementation below conforms to OLE date described in MSDN, e.g. here: https://msdn.microsoft.com/en-us/library/system.datetime.tooadate(v=vs.110).aspx
Implementation below does conform to the MSDN documentation.
It converts a BigDecimal value to Joda LocalDateTime.
BigDecimal is better than float and double since it is able to hold a precise value.
class COMDateToRegularDateConverter {
private static final LocalDateTime ZERO_COM_TIME = new LocalDateTime(1899, 12, 30, 0, 0);
private static final BigDecimal MILLIS_PER_DAY = new BigDecimal(86400000);
LocalDateTime toLocalDateTime(BigDecimal comTime) {
BigDecimal daysAfterZero = comTime.setScale(0, RoundingMode.DOWN);
BigDecimal fraction = comTime.subtract(daysAfterZero).abs(); //fraction always represents the time of that day
BigDecimal fractionMillisAfterZero = fraction.multiply(MILLIS_PER_DAY).setScale(0, RoundingMode.HALF_DOWN);
return ZERO_COM_TIME.plusDays(daysAfterZero.intValue()).plusMillis(fractionMillisAfterZero.intValue());
}
}
This Old New Thing blog entry seems to be a decent treatise on the topic:
The OLE automation date format is a floating point value, counting days since midnight 30 December 1899. Hours and minutes are represented as fractional days.
If you have access to Visual Studio and the MFC COleDateTime source, you can reimplement that in Java.