Related
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);
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.
Does Java have some analog of Oracle's function MONTHS_BETWEEN?
I've run into the same need and started from #alain.janinm answer which is good but doesn't give the exact same result in some cases.
ex :
Consider months between 17/02/2013 and 11/03/2016 ("dd/MM/yyyy")
Oracle result : 36,8064516129032
Java method from #Alain.janinm answer : 36.74193548387097
Here's the changes i made, to get a closer result to Oracle's months_between() function :
public static double monthsBetween(Date startDate, Date endDate){
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
int startDayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
int startMonth = cal.get(Calendar.MONTH);
int startYear = cal.get(Calendar.YEAR);
cal.setTime(endDate);
int endDayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
int endMonth = cal.get(Calendar.MONTH);
int endYear = cal.get(Calendar.YEAR);
int diffMonths = endMonth - startMonth;
int diffYears = endYear - startYear;
int diffDays = endDayOfMonth - startDayOfMonth;
return (diffYears * 12) + diffMonths + diffDays/31.0;
}
With this function the result of the call for the dates 17/02/2013 and 11/03/2016 is : 36.806451612903224
Note : From my understanding Oracle's months_between() function considers that all months are 31 days long
You can do that with :
public static int monthsBetween(Date minuend, Date subtrahend){
Calendar cal = Calendar.getInstance();
cal.setTime(minuend);
int minuendMonth = cal.get(Calendar.MONTH);
int minuendYear = cal.get(Calendar.YEAR);
cal.setTime(subtrahend);
int subtrahendMonth = cal.get(Calendar.MONTH);
int subtrahendYear = cal.get(Calendar.YEAR);
return ((minuendYear - subtrahendYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
(minuendMonth - subtrahendMonth);
}
Edit :
According to this documentation MONTHS_BETWEEN return a fractional result, I think this method do the same :
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date d = sdf.parse("02/02/1995");
Date d2 = sdf.parse("01/01/1995");
System.out.println(monthsBetween(d, d2));
}
public static double monthsBetween(Date baseDate, Date dateToSubstract){
Calendar cal = Calendar.getInstance();
cal.setTime(baseDate);
int baseDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
int baseMonth = cal.get(Calendar.MONTH);
int baseYear = cal.get(Calendar.YEAR);
cal.setTime(dateToSubstract);
int subDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
int subMonth = cal.get(Calendar.MONTH);
int subYear = cal.get(Calendar.YEAR);
//int fullMonth = ((baseYear - subYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
//(baseMonth - subMonth);
//System.out.println(fullMonth);
return ((baseYear - subYear) * (cal.getMaximum(Calendar.MONTH)+1)) +
(baseDayOfYear-subDayOfYear)/31.0;
}
I had to migrate some Oracle code to java and haven't found the analog for months_between oracle function. While testing listed examples found some cases when they produce wrong results.
So, created my own function. Created 1600+ tests comparing results of db vs my function, including dates with time component - all work fine.
Hope, this can help someone.
public static double oracle_months_between(Timestamp endDate,Timestamp startDate) {
//MONTHS_BETWEEN returns number of months between dates date1 and date2.
// If date1 is later than date2, then the result is positive.
// If date1 is earlier than date2, then the result is negative.
// If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer.
// Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String endDateString = sdf.format(endDate), startDateString = sdf.format(startDate);
int startDateYear = Integer.parseInt(startDateString.substring(0,4)), startDateMonth = Integer.parseInt(startDateString.substring(5,7)), startDateDay = Integer.parseInt(startDateString.substring(8,10));
int endDateYear = Integer.parseInt(endDateString.substring(0,4)), endDateMonth = Integer.parseInt(endDateString.substring(5,7)), endDateDay = Integer.parseInt(endDateString.substring(8,10));
boolean endDateLDM = is_last_day(endDate), startDateLDM = is_last_day(startDate);
int diffMonths = -startDateYear*12 - startDateMonth + endDateYear * 12 + endDateMonth;
if (endDateLDM && startDateLDM || extract_day(startDate) == extract_day(endDate)){
// If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer.
return (double)(diffMonths);
}
double diffDays = (endDateDay - startDateDay)/31.;
Timestamp dStart = Timestamp.valueOf("1970-01-01 " + startDateString.substring(11)), dEnd = Timestamp.valueOf("1970-01-01 " + endDateString.substring(11));
return diffMonths + diffDays + (dEnd.getTime()-dStart.getTime())/1000./3600./24./31.;
}
public static boolean is_last_day(Timestamp ts){
Calendar calendar = Calendar.getInstance();
calendar.setTime(ts);
int max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
return max == Integer.parseInt((new SimpleDateFormat("dd").format(ts)));
}
Actually, I think the correct implementation is this one:
public static BigDecimal monthsBetween(final Date start, final Date end, final ZoneId zone, final int scale ) {
final BigDecimal no31 = new BigDecimal(31);
final LocalDate ldStart = start.toInstant().atZone(zone).toLocalDate();
final LocalDate ldEnd = end.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
final int endDay = ldEnd.getDayOfMonth();
final int endMonth = ldEnd.getMonthValue();
final int endYear = ldEnd.getYear();
final int lastDayOfEndMonth = ldEnd.lengthOfMonth();
final int startDay = ldStart.getDayOfMonth();
final int startMonth = ldStart.getMonthValue();
final int startYear = ldStart.getYear();
final int lastDayOfStartMonth = ldStart.lengthOfMonth();
final BigDecimal diffInMonths = new BigDecimal((endYear - startYear)*12+(endMonth-startMonth));
final BigDecimal fraction;
if(endDay==startDay || (endDay==lastDayOfEndMonth && startDay==lastDayOfStartMonth)) {
fraction = BigDecimal.ZERO;
}
else {
fraction = BigDecimal.valueOf(endDay-startDay).divide(no31, scale, BigDecimal.ROUND_HALF_UP);
}
return diffInMonths.add(fraction);
}
public static BigDecimal monthsBetween(final Date start, final Date end) {
return monthsBetween(start, end, ZoneId.systemDefault(), 20);
}
In Joda Time there is a monthsBetween in the org.joda.time.Months class.
I've the same problem and following the Oracle MONTHS_BETWEEN I have made some changes to #alain.janinm and #Guerneen4 answers in order to correct some cases:
Consider months between 31/07/1998 and 30/09/2013 ("dd/MM/yyyy") Oracle result : 182 Java method from #Guerneen4 answer : 181.96774193548387
The problem is that according to specification if date1 and date2 are both last days of months, then the result is always an integer.
For easy understanding here you can find Oracle MONTHS_BETWEEN specifications: https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions089.htm. I copy here to summarize:
"returns number of months between dates date1 and date2. If date1 is later than date2, then the result is positive. If date1 is earlier than date2, then the result is negative. If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2."
Here's the changes that I've done get the closest result to the Oracle's months_between() function :
public static double monthsBetween(Date startDate, Date endDate) {
Calendar calSD = Calendar.getInstance();
Calendar calED = Calendar.getInstance();
calSD.setTime(startDate);
int startDayOfMonth = calSD.get(Calendar.DAY_OF_MONTH);
int startMonth = calSD.get(Calendar.MONTH);
int startYear = calSD.get(Calendar.YEAR);
calED.setTime(endDate);
int endDayOfMonth = calED.get(Calendar.DAY_OF_MONTH);
int endMonth = calED.get(Calendar.MONTH);
int endYear = calED.get(Calendar.YEAR);
int diffMonths = endMonth - startMonth;
int diffYears = endYear - startYear;
int diffDays = calSD.getActualMaximum(Calendar.DAY_OF_MONTH) == startDayOfMonth
&& calED.getActualMaximum(Calendar.DAY_OF_MONTH) == endDayOfMonth ? 0 : endDayOfMonth - startDayOfMonth;
return (diffYears * 12) + diffMonths + diffDays / 31.0;
}
java.time
The other Answers use the troublesome old Calendar class that is now legacy, supplanted by the java.time classes.
MONTHS_BETWEEN
The doc says:
MONTHS_BETWEEN returns number of months between dates date1 and date2. If date1 is later than date2, then the result is positive. If date1 is earlier than date2, then the result is negative. If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
Retrieve a LocalDate from the database using JDBC 4.2 and later. The java.sql.Date class is now legacy, and can be avoided.
LocalDate start = myResultSet.getObject( … , LocalDate.class ) ; // Retrieve a `LocalDate` from database using JDBC 4.2 and later.
For our demo here, let’s simulate those retrieved dates.
LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 23 );
LocalDate stop = start.plusDays( 101 );
Period
Calculate the elapsed time as a span of time unattached to the timeline, a Period.
Period p = Period.between( start , stop );
Extract the total number of months.
long months = p.toTotalMonths() ;
Extract the number of days part, the days remaining after calculating the months.
int days = p.getDays() ;
BigDecimal
For accuracy, use BigDecimal. The double and Double types use floating-point technology, trading away accuracy for fast execution performance.
Convert our values from primitives to BigDecimal.
BigDecimal bdDays = new BigDecimal( days );
BigDecimal bdMaximumDaysInMonth = new BigDecimal( 31 );
Divide to get our fractional month. The MathContext provides a limit to resolving the fractional number, plus a rounding mode to get there. Here we use the constant MathContext.DECIMAL32, because I am guessing the Oracle function is using 32-bit math. The rounding mode is RoundingMode.HALF_EVEN, the default specified by IEEE 754, and also known as “Banker’s rounding” which is more mathematically fair than “schoolhouse rounding” commonly taught to children.
BigDecimal fractionalMonth = bdDays.divide( bdMaximumDaysInMonth , MathContext.DECIMAL32 );
Add this fraction to our number of whole months, for a complete result.
BigDecimal bd = new BigDecimal( months ).add( fractionalMonth );
To more closely emulate the behavior of the Oracle function, you may want to convert to a double.
double d = bd.round( MathContext.DECIMAL32 ).doubleValue();
Oracle did not document the gory details of their calculation. So you may need to do some trial-and-error experimentation to see if this code has results in line with your Oracle function.
Dump to console.
System.out.println( "From: " + start + " to: " + stop + " = " + bd + " months, using BigDecimal. As a double: " + d );
See this code run live at IdeOne.com.
From: 2018-01-23 to: 2018-05-04 = 3.3548387 months, using BigDecimal. As a double: 3.354839
Caveat: While I answered the Question as asked, I must remark: Tracking elapsed time as a fraction as seen here is unwise. Instead use the java.time classes Period and Duration. For textual representation, use the standard ISO 8601 format: PnYnMnDTnHnMnS. For example, the Period seen in our example above: P3M11D for three months and eleven days.
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, Java 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 Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). 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.
The previous answers are not perfect because they do not handle dates such as Feb 31.
Here is my iterative interpretation of MONTHS_BETWEEN in Javascript...
// Replica of the Oracle function MONTHS_BETWEEN where it calculates based on 31-day months
var MONTHS_BETWEEN = function(d1, d2) {
// Don't even try to calculate if it's the same day
if (d1.getTicks() === d2.getTicks()) return 0;
var totalDays = 0;
var earlyDte = (d1 < d2 ? d1 : d2); // Put the earlier date in here
var laterDate = (d1 > d2 ? d1 : d2); // Put the later date in here
// We'll need to compare dates using string manipulation because dates such as
// February 31 will not parse correctly with the native date object
var earlyDteStr = [(earlyDte.getMonth() + 1), earlyDte.getDate(), earlyDte.getFullYear()];
// Go in day-by-day increments, treating every month as having 31 days
while (earlyDteStr[2] < laterDate.getFullYear() ||
earlyDteStr[2] == laterDate.getFullYear() && earlyDteStr[0] < (laterDate.getMonth() + 1) ||
earlyDteStr[2] == laterDate.getFullYear() && earlyDteStr[0] == (laterDate.getMonth() + 1) && earlyDteStr[1] < laterDate.getDate()) {
if (earlyDteStr[1] + 1 < 32) {
earlyDteStr[1] += 1; // Increment the day
} else {
// If we got to this clause, then we need to carry over a month
if (earlyDteStr[0] + 1 < 13) {
earlyDteStr[0] += 1; // Increment the month
} else {
// If we got to this clause, then we need to carry over a year
earlyDteStr[2] += 1; // Increment the year
earlyDteStr[0] = 1; // Reset the month
}
earlyDteStr[1] = 1; // Reset the day
}
totalDays += 1; // Add to our running sum of days for this iteration
}
return (totalDays / 31.0);
};
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
How to get current Century from a date in Java?
For example the date "06/03/2011" according to format "MM/dd/yyyy". How can I get current century from this date using SimpleDateFormat?
Date date = new SimpleDateFormat("MM/dd/yyyy").parse(yourString);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int century = (calendar.get(Calendar.YEAR) / 100) +1;
A slight change to what Harry Lime posted. His logic is not entirely correct. Year 1901 would be 20th century, but 1900 would be 19th century.
public class CenturyYear {
public static void main(String[] args) {
int test = centuryFromYear(1900);
System.out.println(test);
}
static int centuryFromYear(int year) {
if (year % 100 == 0) {
year = year / 100;
} else {
year = (year / 100) + 1;
}
return year;
}
}
The other Answers are correct but outdated.
java.time
The java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.
Now in maintenance mode, the Joda-Time project also 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.
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
To parse specify a formatting pattern. By the way, I suggest using ISO 8601 standard formats which can be parsed directly by java.time classes.
String input = "06/03/2011";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "MM/dd/uuuu" ).withLocale ( Locale.US );
LocalDate ld = LocalDate.parse ( input , f );
To get the century, just take the year number and divide by 100. If you want the ordinal number, "twenty-first century" for 20xx, add one.
int centuryPart = ( ld.getYear () / 100 );
int centuryOrdinal = ( ( ld.getYear () / 100 ) + 1 );
Dump to console.
System.out.println ( "input: " + input + " | ld: " + ld + " | centuryPart: " + centuryPart + " | centuryOrdinal: " + centuryOrdinal );
input: 06/03/2011 | ld: 2011-06-03 | centuryPart: 20 | centuryOrdinal: 21
int century = (year + 99)/ 100;
I dont know anything about Java but why don't you just get the full year and make the last 2 digits 0?
EDIT
If you want 2011 to become 21st century - just get the fully qualified year in string format, then knock off the last 2 characters, then parse to an int and add 1!
You would simply return (year + 99) / 100
Split it by the slahes, get the first two symbols of the third element in the resulting array, Integer.parseInt it and add 1, that is:
String arr = myDate.split("/");
String shortYear = myDate[2].substring(0, 2);
int century = Integer.parseInt(shortYear) + 1;
(not sure about the substring() syntax off the top of my head)