Java: How to convert a string (HH:MM:SS) to a duration? - java

i want to convert a string with a format of HH:MM:SS or MM:SS or SS into a datatype of Duration.
solution:
private ArrayList<Duration> myCdDuration = new ArrayList<Duration>();
private void convert(String aDuration) {
chooseNewDuration(stringToInt(splitDuration(aDuration))); //stringToInt() returns an int[] and splitDuration() returns a String[]
}
private void chooseNewDuration(int[] array) {
int elements = array.length;
switch (elements) {
case 1:
myCdDuration.add(newDuration(true, 0, 0, 0, 0, 0, array[0]));
break;
case 2:
myCdDuration.add(newDuration(true, 0, 0, 0, 0, array[0], array[1]));
break;
case 3:
myCdDuration.add(newDuration(true, 0, 0, 0, array[0], array[1],
array[2]));
break;
}
}
thanks for help ... any easier way to do that ? -> create your own Duration class:
public class Duration {
private int intSongDuration;
private String printSongDuration;
public String getPrintSongDuration() {
return printSongDuration;
}
public void setPrintSongDuration(int songDuration) {
printSongDuration = intToStringDuration(songDuration);
}
public int getIntSongDuration() {
return intSongDuration;
}
public void setIntSongDuration(int songDuration) {
intSongDuration = songDuration;
}
public Duration(int songDuration) {
setIntSongDuration(songDuration);
}
Converts the int value into a String for output/print:
private String intToStringDuration(int aDuration) {
String result = "";
int hours = 0, minutes = 0, seconds = 0;
hours = aDuration / 3600;
minutes = (aDuration - hours * 3600) / 60;
seconds = (aDuration - (hours * 3600 + minutes * 60));
result = String.format("%02d:%02d:%02d", hours, minutes, seconds);
return result;
}

tl;dr
No need to define your own Duration class, as Java provides one.
Duration.between ( // Represent a span of time of hours, minutes, seconds.
LocalTime.MIN , // 00:00:00
LocalTime.parse ( "08:30:00" ) // Parse text as a time-of-day.
) // Returns a `Duration` object, a span-of-time.
.toString() // Generate a `String` with text in standard ISO 8601 format.
PT8H30M
And parse standard ISO 8601 formatted text.
Duration.parse( "PT8H30M" ) // Parse standard ISO 8601 text yo get a `Duration` object.
Avoid HH:MM:SS format
If by the string 08:30:00 you mean "eight and a half hours" span of time rather than a time-of-day “half-past eight in the morning”, then avoid that format of HH:MM:SS. That format ambiguous, appearing to be a time-of-day. Instead use the standard ISO 8601 format discussed below.
Duration and time-of-day are two very different concepts. You must be clear on them, each should be distinct in your mind. Using the ambiguous format of HH:MM:SS makes that distinction all the more difficult (so avoid that format!).
java.time
The modern way is with the java.time classes.
LocalTime
First parse your string as a LocalTime. This class represents a time-of-day without a date and without a time zone. Having no time zone means these objects are based on a generic 24-hour clock without regard for anomalies such as Daylight Saving Time (DST).
We do not really want a LocalTime as your input string represents a span of time rather than a time-of-day. But this is just the first step.
LocalTime lt = LocalTime.parse ( "08:30:00" );
Duration
To represent the desired span-of-time, we want the Duration class. This class is for spans of time not attached to the timeline. We can create one by converting that LocalTime via getting the amount of time from the beginning of the time-of-day clock, 00:00:00.0 or LocalTime.MIN, and the lt we just instantiated.
Duration d = Duration.between ( LocalTime.MIN , lt );
Editing the input string
The approach above using LocalTime only works if your input strings represent a duration of less than 24 hours. If over 24 hours, you will parse the input string yourself.
Something like the following code. Of course the actual parsing depends on resolving the ambiguity of your particular input string. Is 50:00 meant to be fifty hours or fifty minutes? (This ambiguity is a strong reason to avoid this confusing format whenever possible, and stick with ISO 8601 formats.)
String input = "50:00"; // Or "50:00:00" (fifty hours, either way)
String[] parts = input.split ( ":" );
Duration d = Duration.ZERO;
if ( parts.length == 3 ) {
int hours = Integer.parseInt ( parts[ 0 ] );
int minutes = Integer.parseInt ( parts[ 1 ] );
int seconds = Integer.parseInt ( parts[ 2 ] );
d = d.plusHours ( hours ).plusMinutes ( minutes ).plusSeconds ( seconds );
} else if ( parts.length == 2 ) {
int hours = Integer.parseInt ( parts[ 0 ] );
int minutes = Integer.parseInt ( parts[ 1 ] );
d = d.plusHours ( hours ).plusMinutes ( minutes );
} else {
System.out.println ( "ERROR - Unexpected input." );
}
ISO 8601
We can see the result by generating a String in standard ISO 8601 format for durations by simply calling Duration::toString. The java.time classes use ISO 8601 by default when parsing/generating strings. For durations, the standard format is PnYnMnDTnHnMnS where the P marks the beginning and the T separates the years-months-days portion from the hours-minutes-seconds portion. So, our eight-and-a-half hours will appear as PT8H30M.
System.out.println ( "d.toString(): " + d );
d.toString(): PT8H30M
Collecting Duration objects
You can make a List holding elements of the type Duration.
List<Duration> durations = new ArrayList<>( 3 ); // Initial capacity of 3 elements.
durations.add( d ) ;
durations.add( Duration.between ( LocalTime.MIN , LocalTime.parse ( "03:00:00" ) ) ) ;
durations.add( Duration.between ( LocalTime.MIN , LocalTime.parse ( "01:15:00" ) ) ) ;
durations.toString(): [PT8H30M, PT3H, PT1H15M]
Remember that the strings you see in that output like PT8H30M are just that: output of generated strings. The Duration type is not a simple string but rather generates a String object by its toString method.
If you stick to the ISO 8601 formats, you can easily parse as well as generate such strings. No need to go through the LocalTime conversion rigamarole we performed at the top of this Answer.
Duration d = Duration.parse( "PT8H30M" );
See this example code live in IdeOne.com.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

I assume what you're ultimately trying to achieve is to compute the duration of the CD in seconds.
There are several ways to do this, but I think the most straightforward is to just split on : to get the hours, minutes, and seconds fields, then to compute the duration manually:
String timestampStr = "14:35:06";
String[] tokens = timestampStr.split(":");
int hours = Integer.parseInt(tokens[0]);
int minutes = Integer.parseInt(tokens[1]);
int seconds = Integer.parseInt(tokens[2]);
int duration = 3600 * hours + 60 * minutes + seconds;

java.time.Duration
A couple of other answers have already mentioned that the Duration class from java.time, the modern Java date and time API, is the class to use for a duration. There are some ways to parse your strings into a Duration, and it’s honestly not clear which one is the best. I’d like to present my way.
Basic
I start simple with just one possible format, hh:mm:ss, for example 01:30:41 for 1 hour 30 minutes 41 seconds.
String durationString = "01:30:41";
String iso = durationString.replaceFirst(
"^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S");
Duration dur = Duration.parse(iso);
System.out.format("%-10s Total %2d minutes or %4d seconds%n",
dur, dur.toMinutes(), dur.toSeconds());
Output so far is:
PT1H30M41S Total 90 minutes or 5441 seconds
The Duration.parse method requires ISO 8601 format. It goes like PT1H30M41S for 1 hour 30 minutes 41 seconds. So what I do is I convert your string into this format through a regular expression. The $1, etc., in my replacement string will be substituted by what was matched by the groups in round brackets in the regular expression. So durationString.replaceFirst() converts your string to PT01H30M41S, which Duration can parse.
Three formats
You asked for conversion of HH:MM:SS or MM:SS or SS. The modification to the above is actually quite simple: we just need three calls to replaceFirst() instead of one. Exactly one of them will succeed in replacing anything. The other two will just return the string unchanged.
String[] durationStrings = { "01:32:43", "26:31", "14" };
for (String durationString : durationStrings) {
String iso = durationString.replaceFirst("^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S")
.replaceFirst("^(\\d{2}):(\\d{2})$", "PT$1M$2S")
.replaceFirst("^(\\d{2})$", "PT$1S");
Duration dur = Duration.parse(iso);
System.out.format("%-10s Total %2d minutes or %4d seconds%n",
dur, dur.toMinutes(), dur.toSeconds());
}
PT1H32M43S Total 92 minutes or 5563 seconds
PT26M31S Total 26 minutes or 1591 seconds
PT14S Total 0 minutes or 14 seconds
Time4J and net.time4j.Duration
In case you’re fine with an external dependency, the Time4J library offers a much more elegant way of parsing your strings to duration objects. We first declare a formatter:
private static final Duration.Formatter<ClockUnit> FORMATTER
= Duration.formatter(ClockUnit.class, "[[hh:]mm:]ss");
The square brackets in the format pattern string surround optional parts, so this formatter accepts all of hh:mm:ss, mm:ss and just ss.
for (String durationString : durationStrings) {
Duration<ClockUnit> dur = FORMATTER.parse(durationString);
long minutes = dur.with(ClockUnit.MINUTES.only())
.getPartialAmount(ClockUnit.MINUTES);
long seconds = dur.with(ClockUnit.SECONDS.only())
.getPartialAmount(ClockUnit.SECONDS);
System.out.format("%-10s Total %2d minutes or %4d seconds%n",
dur, minutes, seconds);
}
Output is the same as before:
PT1H32M43S Total 92 minutes or 5563 seconds
PT26M31S Total 26 minutes or 1591 seconds
PT14S Total 0 minutes or 14 seconds
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Time4J - API, Tutorials and Examples

Your myCdDuration is confusing. Do you want one Duration object equivalent to whatever was specified in the string, or a list of Duration objects where the first contains the hours, the second minutes etc?
You can't just cast a String into some other object. You should parse the value into an numeric type and use DataTypeFactory to construct the Duration object.

I would suggest not using javax.xml.datatype.Duration, as its related to the XML Java API and it's confusing to use it if you are not dealing with XML. Moreover, it is an abstract class, and there's no non-abstract documented implementation of it in Java SE, so you'd have to either create your own non-abstract implementation or obtain an instance somehow (probably, playing with the XML API).
You manage time and dates in Java using the Date and Calendar classes. To convert Strings to Date/Calendar you use DateFormat or SimpleDateFormat. That will let you perform your duration arithmetic, although that's not 100% pretty.
Mansoor provides a way to do stuff manually using String manipulation and handling durations as numeric values- if you only do simple stuff, it might be more straightforward to do that.
If you have to perform more complex stuff, you might want to look into http://joda-time.sourceforge.net/

I have written this method in my utils class to parse various kind of duration strings. It is quite flexible :
public static int getSecondsFromFormattedDuration(String duration){
if(duration==null)
return 0;
try{
Pattern patternDuration = Pattern.compile("\\d+(?::\\d+){0,2}");
int hours = 0;
int minutes = 0;
int seconds = 0;
if(patternDuration.matcher(duration).matches()){
String[] tokens = duration.split(":");
if(tokens.length==1){
seconds = Integer.parseInt(tokens[0]);
}else if(tokens.length == 2){
minutes = Integer.parseInt(tokens[0]);
seconds = Integer.parseInt(tokens[1]);
}else{
hours = Integer.parseInt(tokens[0]);
minutes = Integer.parseInt(tokens[1]);
seconds = Integer.parseInt(tokens[2]);
}
return 3600 * hours + 60 * minutes + seconds;
}else
return 0;
}catch (NumberFormatException ignored){
return 0;
}
}
This is how it parsed these durations :
"1" --> 1
"10" --> 10
"10:" --> 0 (not a valid duration)
"10:07" --> 607
"06:08" --> 368
"7:22" --> 442
":22" --> 0 (not a valid duration)
"10:32:33" --> 37953
"2:33:22" --> 9202
"2:2:02" --> 7322
"2:33:43:32" --> 0 (not a valid duration)
"33ff" --> 0 (not a valid duration)
"2d:33" --> 0 (not a valid duration)

With Java 8 and Java.time.Duration you can do this given that the string is of the format HH:MM:SS or MM:SS or SS
Duration.ofSeconds(Arrays.stream(runtime.split(":"))
.mapToInt(n -> Integer.parseInt(n))
.reduce(0, (n, m) -> n * 60 + m));

Sample, Convert Current DateTime to Duration in Java 7.
DatatypeFactory.newInstance().newDuration(Calendar.getInstance().getTimeInMillis())
Output -
P48Y5M13DT19H59M24.658S

Related

Java calculate time by adding specific number of hours, but consider only working hours and non weekend days

For example I have sentTime as an input (25 May 2021 02:00:00 PM) and I need to add reviewTime (10 hours) and calculate releasingTime (considering only working hours(9am-5pm) and non weekend days)
25 May 2021 02:00:00 PM + 10 hours would be 26 May 2021 04:00:00 PM
java.time
I do not know of any easy way to do this. The java.time classes have all the parts needed, but you would have to build up some code to do the calculations.
Be aware that you must account for time zone. On some dates, you will encounter anomalies such as days being 23 or 25 hours long, the clock skipping ahead or dropping behind. One example of such anomalies is Daylight Saving Time (DST), but that is not the only cause. Politicians around the world have shown a penchant for redefining the time-keeping of their jurisdictions for varied reasons.
Here is a brief example to get you started, if you choose to go this route.
Besides the java.time classes built into Java, this code also leverages the ThreeTen-Extra library which adds functionality to java.time. We need that library for two classes here:
A TemporalAdjuster for finding the next working day (skipping Saturday-Sunday). See tutorial on temporal adjusters. Tip: You may want to consider implementing a TemporalAdjuster on your own as part of a real solution — but I'm not sure, just an idea I have not thought through.
Interval class to track a pair of moments as seen in UTC (an offset of zero hours-minutes-seconds). Not required here, but might be useful in further work.
Duration work = Duration.ofHours( 10 );
LocalTime shiftStart = LocalTime.of( 9 , 0 );
LocalTime shiftEnd = LocalTime.of( 17 , 0 );
ZoneId z = ZoneId.of( "America/Chicago" );
ZonedDateTime startOfWork = ZonedDateTime.of( 2021 , 5 , 25 , 14 , 0 , 0 , 0 , z );
// Calculate how much time left in the day to work.
ZonedDateTime endOfDayOne = startOfWork.with( shiftEnd );
Duration untilEndOfDayOne = Duration.between( startOfWork , endOfDayOne );
Duration remainingWork = work.minus( untilEndOfDayOne );
// Determine next work-day.
// Add ThreeTen-Extra library to your project to access the `TemporalAdjuster` for `nextWorkingDay()`.
LocalDate nextWorkDay = endOfDayOne.toLocalDate().with( org.threeten.extra.Temporals.nextWorkingDay() );
ZonedDateTime startOfNextWorkingDay = ZonedDateTime.of( nextWorkDay , shiftStart , z );
ZonedDateTime endOfWork = startOfNextWorkingDay.plus( remainingWork );
org.threeten.extra.Interval workInterval =
org.threeten.extra.Interval.of(
startOfWork.toInstant() ,
endOfWork.toInstant()
);
Dump to console. By default, java.time generates text in standard ISO 8601 formats.
System.out.println( "startOfWork = " + startOfWork );
System.out.println( "work = " + work );
System.out.println( "endOfWork = " + endOfWork );
System.out.println( "workInterval = " + workInterval );
When run.
startOfWork = 2021-05-25T14:00-05:00[America/Chicago]
work = PT10H
endOfWork = 2021-05-26T16:00-05:00[America/Chicago]
workInterval = 2021-05-25T19:00:00Z/2021-05-26T21:00:00Z
Project management software
Project Management software is built to do this very job: Calculate elapsed time for various tasks restricted by working hours and working days. One possible solution is trying to leverage such a library for your purposes.
Assuming sentTime is of type java.util.Date, you can may be use the following code that utilizes Java 8's java.time.LocalDateTime
int reviewTime = 10;
List<DayOfWeek> weekends = Arrays.asList(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
LocalDateTime start = sentTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
final int workingHoursStart = 9;
final int workingHoursEnd = 17;
int hoursReviewed = 0;
while(reviewTime > hoursReviewed){
DayOfWeek dayOfWeek = start.getDayOfWeek();
if(weekends.contains(dayOfWeek) || start.getHour() < workingHoursStart || start.getHour() > workingHoursEnd){
start = start.plusHours(1);
continue;
}
start = start.plusHours(1);
hoursReviewed++;
}
Your resultant releasingTime time would be in the start object after the loop finishes iterating.

What is the jconsole time format?

I have monitored my java program using jconsole recently. I saved CPU usage data as a csv file. This is what I've got:
Time,CPU Usage
43690.008014,1,8
43690.008060,0,1
43690.008106,0,1
43690.008153,0,1
43690.008199,0,1
43690.008245,0,1
The CPU Usage column is clear, but I cannot say the same thing about the Time column. What is 43690.008014? How can I parse it into Date? I haven't seen anything like this in my life.
The duration recorded in the CSV file is the number of days since 1899-12-311. In order to get the current date, you can add this duration to LocalDate.of(1899, 12, 30).atStartOfDay().atOffset(ZoneOffset.UTC). I recommend you use java.time.Duration which is modelled on ISO-8601 standards and was introduced with Java-8 as part of JSR-310 implementation. The function, Duration#ofNanos gives you a Duration representing the specified number of nanoseconds. The reason why I am recommending you to use this function despite the fact that there is already a function, Duration#ofDays is that these functions take a long value as the argument and if you cast the duration in your log file (e.g. 43690.008014) to long, its fractional part will be lost giving you an incorrect result.
Therefore, convert these days to nanoseconds, get Duration from the resulting nanoseconds and add the same to LocalDate.of(1899, 12, 30).atStartOfDay().atOffset(ZoneOffset.UTC) to get the current date and time in UTC.
Demo:
import java.time.Duration;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
public class Main {
public static void main(String[] args) {
OffsetDateTime startDateTime = LocalDate.of(1899, 12, 30).atStartOfDay().atOffset(ZoneOffset.UTC);
OffsetDateTime current = startDateTime
.plus(Duration.ofNanos((long) (43690.008014 * 24 * 60 * 60 * 1000_000_000)));
System.out.println(current);
}
}
Output:
2019-08-13T00:11:32.409600512Z
Learn about the modern date-time API from Trail: Date Time.
For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.
If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
1 Why is 1899-12-30 the zero date in Access / SQL Server instead of 12/31?
The time format is daysSinceEpoch.fractionOfCurrentDay
The daysSinceEpoch starting at 1899-12-30 instead of 1900-01-01
The fractionOfCurrentDay is a millionth of the current day, ranging from zero to 999999 (23:59:59)
You can use the following function to convert to LocalDateTime:
public static LocalDateTime convert( final String jconsoleDateFormat )
{
String[] split = jconsoleDateFormat.split( "\\." );
long daysSinceEpoch = Long.parseLong( split[0] );
long dayFraction = Long.parseLong( split[1] );
LocalDateTime epochDate = LocalDateTime.of( 1899,12,30, 0,0 );
LocalDateTime currentDate = epochDate.plusDays( daysSinceEpoch );
long secondsADay = 24 * 60 * 60L; // 86_400
long secondsSinceDayStarted = secondsADay * dayFraction / 1_000_000;
return currentDate.plusSeconds( secondsSinceDayStarted );
}
public static void main( String[] args )
{
System.out.println( convert( "43690.008014" ) ); // 2019-08-13T00:11:32
}

How do i get int value from Date.getTime() (long value)

[purpose]
How to get int value after dividing 2 values(long-type).
[problem]
I changed the time(todaySeatedEndDateStr's HH:mm:ss part), but it is impossible to obtain an accurate value.
And I'm not sure that value is correct.
The main formula>
c'' = b / (a+b) * c
a, b : long type
c : int type
c'' : int type
Finally I want to get C''
Situation pic
# Test Code
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeCalculateTest {
public static void main(String[] args) throws Exception {
//2016-09-20 00:00:00 (Today's start point)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String todayStartDateStr = "2016-09-20 00:00:00";
Date todayStartDate = sdf.parse(todayStartDateStr);
//2016-09-19 23:30:00 (Yesterday's particular point)
String yesterdaySeatedStartDateStr = "2016-09-19 23:30:00";
Date yesterdaySeatedStartDate = sdf.parse(yesterdaySeatedStartDateStr);
//2016-09-20 03:30:00 (Today's particular point)
String todaySeatedEndDateStr = "2016-09-20 21:30:00";
Date todaySeatedEndDate = sdf.parse(todaySeatedEndDateStr);
System.out.println("Today's Start Date String : " + todayStartDateStr);
System.out.println("Today's Start Date Long: " + todayStartDate.getTime());
System.out.println("Yesterday's Start Date String : " + yesterdaySeatedStartDateStr);
System.out.println("Yesterday Start Date Long : " + yesterdaySeatedStartDate.getTime());
System.out.println("Today's End Date String : " + todaySeatedEndDateStr);
System.out.println("Today's End Date Long : " + todaySeatedEndDate.getTime());
int c = 500; // <------ c
System.out.println("c: " + c);
if (yesterdaySeatedStartDate.compareTo(todayStartDate) < 0) {
long a = yesterdaySeatedStartDate.getTime(); // <----- a
long b = todaySeatedEndDate.getTime(); // <------ b
long abSum = a + b; // <------ a+b
System.out.println("Yesterday's long value : " + a);
System.out.println("Today's long value : " + b);
System.out.println("---> Sum : " + abSum);
long result = (long) ((float)b / (float)abSum * c);
System.out.println("---> Result : " + result);
System.out.println("------->to int : " + (int)result );
}
}
}
output >
Today's Start Date String : 2016-09-20 00:00:00
Today's Start Date Long: 1474297200000
Yesterday's Start Date String : 2016-09-19 23:30:00
Yesterday Start Date Long : 1474295400000
Today's End Date String : 2016-09-20 21:30:00
Today's End Date Long : 1474374600000
c: 500
Yesterday's long value : 1474295400000
Today's long value : 1474374600000
---> Sum : 2948670000000
---> Result : 250
------->to int : 250
I changed the 'todaySeatedEndDateStr' variable's HH:mm:ss,
but always get the 250.
How can I fix this problem?
plz help me..
a and b are both measured in milliseconds since January 1970, so they are roughly the same even though a is yesterday and b is today. So b / (a + b) is very close to a half, and half of 500 is 250.
If I interpret your graph correctly, you want to do this instead:
long midnight = todayStartDate.getTime();
long result = (long) ((float) (b - midnight) / (float) (b - a) * c);
Now I am taking the time since midnight in proportion to the time since a yesterday. Please try and see if it works for you.
Midnight of earlier period’s start to end of later period?
Looks like you have over-complicated the original problem. Seems your problem picture wants elapsed time from midnight of the date of the earlier period to a later moment. If not so, please edit your Question to state in plain conversational English what is the problem statement. And why do you use words "yesterday" and "today" if the date-time values are hard-coded?
java.time
You are using troublesome old legacy date-time classes now supplanted by the java.time classes.
We parse as LocalDateTime objects because your inputs lack info about offset-from-UTC or time zone. If you want to account for issues such as Daylight Saving Time (DST), use ZonedDateTime instead.
To parse, we replace the SPACE in the middle with a T to comply with ISO 8601 standard.
LocalDateTime earlierStart = LocalDate.parse( "2016-09-19 23:30:00".replace( " " , "T" ) );
LocalDateTime laterStop = LocalDate.parse( "2016-09-20 21:30:00".replace( " " , "T" ) );
To get the midnight ending of the starting point, we need to go through the LocalDate. We move to the start of the next day because getting the last moment of the day is problematic with an endlessly divisible fractional second.
LocalDate localDateOfStartNextDay = earlierStart.toLocalDate().plusDays( 1 );
LocalDateTime newDayAfterStart = localDateOfStartNextDay.atStartOfDay();
Now capture the elapsed time as a Duration with a resolution of nanoseconds.
Duration duration = Duration.between( newDayAfterStart , laterStop );
This code may not be exactly your solution, given that your Question is confusing. But I think you can see that working with the java.time classes will be less convoluted that trying to do math on count-from-epoch numbers.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP (see How to use…).
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Java date comparison off by a day

I have a Java method which compares two Dates and returns the number of days between them, but it's off by a day.
Even after I 0 out the hours, min, and sec the calculation is still off.
public long compareDates(Date exp, Date today){
TimeZone tzone = TimeZone.getTimeZone("America/New_York");
Calendar expDate = Calendar.getInstance();
Calendar todayDate = Calendar.getInstance();
expDate.setTime(exp);
todayDate.setTime(today);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
logger.info("Today = " + Long.toString(todayDate.getTimeInMillis()) + " Expiration = " + Long.toString(expDate.getTimeInMillis()));
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
return (expDate.getTimeInMillis()-todayDate.getTimeInMillis())/86400000;
}
Output
Today = 1453939200030 Expiration = 1454544000000
There's 7 days between 1/28 and 2/4 but this returns 6.
Well, as you can see, you didn't clear the milliseconds, and 1454544000000 - 1453939200030 = 604799970 and dividing by 86400000 gets you 6.99999965277777..., which means 6 when truncated to int.
Now, if you clear the milliseconds too, today becomes 1453939200000, which will lead to you answer 7.
Note: This doesn't mean you're done, because of Daylight Savings Time. With DST, one of the timestamps may be ±1 hour from the other, so you may still get that truncation issue.
This was an answer to your particular issue. Try searching for how to correctly find days between dates in Java.
Today = 1453939200030
The times are given in milliseconds, and it looks like somehow your inputted Date has 30 extra milliseconds on it.
When I subtract the 30 milliseconds, then do the math on a calculator, I get 7 days. With your figures as is, I get 6.9999996527777777777777777777778, and in long math, the decimal figures get truncated to 6.
Zero out the milliseconds also.
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
java.time
The Question and other Answers use outmoded classes. The old date-time classes such as java.util.Date/.Calendar bundled with the earliest versions of Java have proven to be quite troublesome. Those old classes have been supplanted by the java.time framework in Java 8 and later.
As the other Answers point out correctly, the issue is that the start long has 30 on the right side, precluding a whole-day calculation.
Count-Of-Days Definition
Furthermore you must define what you mean by a count-of-days. Do you mean a count by date, so any time on the 3rd of January to any time on the 4th is one day even if the times were a minute before and after midnight? Or do you mean a count of generic 24-hour blocks of time while ignoring the fact that particular days in particular time zones are not always 24-hours long because of Daylight Saving Time (DST) and other anomalies?
Count Days By Date
If you want the former, count by dates, then make use of the LocalDate class (a date-only without time-of-day nor time zone) and the Period class (a span of time defined as a count of years, months, days) found in java.time.
Define your inputs. Use long rather than int. These numbers apparently represent a count of milliseconds since the first moment of 1970 in UTC.
long startMilli = 1_453_939_200_030L;
long stopMilli = 1_454_544_000_000L;
Convert those long numbers into Instant objects, a moment on the timeline in UTC.
Instant startInstant = Instant.ofEpochMilli ( startMilli );
Instant stopInstant = Instant.ofEpochMilli ( stopMilli );
Define the time zone in which you want to consider the calendar dates. Note that time zone is crucial in defining dates. The date is not simultaneously the same around the globe. The date varies by time zone.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
Apply that time zone to each Instant to produce ZonedDateTime.
ZonedDateTime startZdt = ZonedDateTime.ofInstant ( startInstant , zoneId );
ZonedDateTime stopZdt = ZonedDateTime.ofInstant ( stopInstant , zoneId );
To get a Period, we need “local” dates. By “local” we mean any particular locality, a generic date value. The LocalDate class contains no time zone, but the time zone contained with in the ZonedDateTime is applied when determining a LocalDate.
LocalDate startLocalDate = startZdt.toLocalDate ();;
LocalDate stopLocalDate = stopZdt.toLocalDate ();
Define our span of time as a count of generic days, in Period.
Period period = Period.between ( startLocalDate , stopLocalDate );
Interrogate the Period to ask for the number of generic days contained within.
int days = period.getDays ();
Dump to console.
System.out.println ( "milli: " + startMilli + "/" + stopMilli + " | Instant: " + startInstant + "/" + stopInstant + " | ZonedDateTime: " + startZdt + "/" + stopZdt + " | LocalDate: " + startLocalDate + "/" + stopLocalDate + " | period: " + period + " | days: " + days );
milli: 1453939200030/1454544000000 | Instant: 2016-01-28T00:00:00.030Z/2016-02-04T00:00:00Z | ZonedDateTime: 2016-01-27T19:00:00.030-05:00[America/Montreal]/2016-02-03T19:00-05:00[America/Montreal] | LocalDate: 2016-01-27/2016-02-03 | period: P7D | days: 7
Count Of Whole Days
If you want a count of whole days, use the Days class from ThreeTen-Extra. Notice in the output below that we get a count of six (6) days rather than seven (7) as seen above.
ThreeTen-Extra
The ThreeTen-Extra project extends java.time. Run by the same folks who built java.time.
The behavior of the between method is not documented clearly. Experimenting shows that it seems to based on 24-hour chunks of time, not dates. Replace the 030 with 000, and also try replacing in the stopMilli the last 000 with 030, to see the behavior for yourself.
Days daysObject = Days.between ( startZdt , stopZdt );
int daysObjectCount = daysObject.getAmount ();
Dump to console. The P6D string you see in the output was generated according to the formats defined in the ISO 8601 standard. This standard is used by default in java.time for all parsing and generating of textual representations of date-time values. These standard formats are quite sensible and useful so do glance at that linked Wikipedia page.
System.out.println ( "daysObject: " + daysObject + " | daysObjectCount: " + daysObjectCount );
daysObject: P6D | daysObjectCount: 6
To fix my problems, I have zeroed out the milliseconds as mentioned, as well as casted the longs to doubles in order to maintain accuracy and round when necessary.
expDate.setTime(exp);
todayDate.setTime(today);
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
double diff = ((double)expDate.getTimeInMillis()-(double)todayDate.getTimeInMillis())/86400000;
return Math.round(diff);

Java TimeZone head scratching

I would like to save some user timezone in a Daylight saving proof format.
My goal is to get the correct GMT offset whenever the code gets executed.
In order to figure out my best option, I wrote the following:
ArrayList<String> list = new ArrayList<String>();
list.add( "EST");
list.add( "EDT");
list.add( "America/New_York");
long now = System.currentTimeMillis();
for( String tzID: list) {
TimeZone tz = TimeZone.getTimeZone( tzID);
System.out.println( tzID + " now=" + tz.getOffset( now) / 3600000 + " / +182=" + tz.getOffset( now + ( 182 * 86400000)) / 3600000);
}
For short, give me the offset now and in 182 days
Executed September 3rd, the output is
EST now=-5 / +182=-5
EDT now=0 / +182=0
America/New_York now=-4 / +182=-4
This is unexpected for several reasons
1) Why is America/New_York not giving -4/-5 ?, Isn't it supposed to be date sensitive?
2) Why does EDT == UTC?
java.time
The question and the accepted answer use the java.util date-time API which was the right thing to do in 2012. In March 2014, the modern Date-Time API was released as part of the Java 8 standard library which supplanted the legacy date-time API and since then it is strongly recommended to switch to java.time, the modern date-time API.
Solution using java.time
You can use ZonedDateTime which automatically adjusts the time zone offset for a given ZoneId.
Demo:
import java.time.ZoneId;
import java.time.ZonedDateTime;
class Main {
public static void main(String[] args) {
ZoneId zone = ZoneId.of("America/New_York");
ZonedDateTime now = ZonedDateTime.now(zone);
ZonedDateTime after182Days = now.plusDays(182);
System.out.println(zone + " now=" + now.getOffset() + " / +182=" + after182Days.getOffset());
}
}
Output as of now:
America/New_York now=-05:00 / +182=-04:00
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
Do not use three-letter timezone ID: Note from the Java 7 Timezone documentation:
Three-letter time zone IDs
For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such as "PST", "CTT", "AST") are
also supported. However, their use is deprecated because the same
abbreviation is often used for multiple time zones (for example, "CST"
could be U.S. "Central Standard Time" and "China Standard Time"), and
the Java platform can then only recognize one of them.
One problem you have is that 182 * 86400000 overflows. If you use
long now = System.currentTimeMillis();
for( String tzID: "EST,EDT,America/New_York".split(",")) {
TimeZone tz = TimeZone.getTimeZone( tzID);
System.out.println( tz.getDisplayName() + " now=" + tz.getOffset( now) / 36e5
+ " / +182=" + tz.getOffset( now + 182 * 86400000L) / 36e5);
}
prints
Eastern Standard Time now=-5.0 / +182=-5.0
Greenwich Mean Time now=0.0 / +182=0.0
Eastern Standard Time now=-4.0 / +182=-5.0
If you look at the javadoc and source for getTimeZone you can see
* #return the specified <code>TimeZone</code>, or the GMT zone if the given ID
* cannot be understood.
public static synchronized TimeZone getTimeZone(String ID) {
return getTimeZone(ID, true);
}
private static TimeZone getTimeZone(String ID, boolean fallback) {
TimeZone tz = ZoneInfo.getTimeZone(ID);
if (tz == null) {
tz = parseCustomTimeZone(ID);
if (tz == null && fallback) {
tz = new ZoneInfo(GMT_ID, 0);
}
}
return tz;
}
In short, EDT is not recognised so it becomes GMT.
I suspect this is the problem:
now + ( 182 * 86400000)
The parenthesized arithmetic expression overflows 32 bits. You probably want:
now + ( 182 * 86400000L)
However, that still assumes that any daylight saving time will be applied for roughly six months, which is certainly not the case in the real world. For example, looking at the Sao Paolo time zone, it switches in October and February - so if you ran your code in September, you'd end up seeing -3 / -3. Even for time zones where DST switches on/off roughly every six months, you're very likely to find 182 consecutive days each year without a switchover (almost by definition, given that that's slightly less than half a year).
It's not clear exactly what you're trying to do, but I suspect you should really just be saving the time zone ID, e.g. "America/New_York". Almost anything else is asking for trouble.

Categories