Skip to content

Commit cf68538

Browse files
committed
- Updated implementation of the getMonthsBetween() method
git-svn-id: svn://192.168.0.80/JavaXT/javaxt-core@1592 2c7b0aa6-e0b2-3c4e-bb4a-8b65b6c465ff
1 parent 27cab44 commit cf68538

File tree

1 file changed

+185
-43
lines changed

1 file changed

+185
-43
lines changed

src/javaxt/utils/Date.java

Lines changed: 185 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public class Date implements Comparable {
104104
//**************************************************************************
105105
//** Constructor
106106
//**************************************************************************
107-
/** Creates a new instance of date using current time stamp
107+
/** Creates a new instance of this class using the current time
108108
*/
109109
public Date(){
110110
currDate = new java.util.Date();
@@ -114,7 +114,7 @@ public Date(){
114114
//**************************************************************************
115115
//** Constructor
116116
//**************************************************************************
117-
/** Creates a new instance of date using supplied java.util.Date
117+
/** Creates a new instance of this class using a java.util.Date
118118
*/
119119
public Date(java.util.Date date){
120120
if (date==null) throw new IllegalArgumentException("Date is null.");
@@ -125,7 +125,7 @@ public Date(java.util.Date date){
125125
//**************************************************************************
126126
//** Constructor
127127
//**************************************************************************
128-
/** Creates a new instance of date using supplied java.util.Calendar
128+
/** Creates a new instance of this class using a java.util.Calendar
129129
*/
130130
public Date(Calendar calendar){
131131
if (calendar==null) throw new IllegalArgumentException("Calendar is null.");
@@ -137,7 +137,26 @@ public Date(Calendar calendar){
137137
//**************************************************************************
138138
//** Constructor
139139
//**************************************************************************
140-
/** Creates a new instance of date using a timestamp (in milliseconds)
140+
/** Creates a new instance of this class using a java.time.LocalDate
141+
*/
142+
public Date(java.time.LocalDate date){
143+
if (date==null) throw new IllegalArgumentException("Date is null.");
144+
Calendar cal = Calendar.getInstance();
145+
cal.set(Calendar.YEAR, date.getYear());
146+
cal.set(Calendar.MONTH, date.getMonthValue()-1);
147+
cal.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth());
148+
cal.set(Calendar.HOUR_OF_DAY, 0);
149+
cal.set(Calendar.MINUTE, 0);
150+
cal.set(Calendar.SECOND, 0);
151+
cal.set(Calendar.MILLISECOND, 0);
152+
currDate = cal.getTime();
153+
}
154+
155+
156+
//**************************************************************************
157+
//** Constructor
158+
//**************************************************************************
159+
/** Creates a new instance of this class using a timestamp (in milliseconds)
141160
* since 1/1/1970.
142161
*/
143162
public Date(long milliseconds){
@@ -150,7 +169,8 @@ public Date(long milliseconds){
150169
//**************************************************************************
151170
//** Constructor
152171
//**************************************************************************
153-
/** Creates a new instance of date using a String representation of a date.
172+
/** Creates a new instance of this class using a String representation of a
173+
* date. Supports multiple common date formats.
154174
*/
155175
public Date(String date) throws ParseException {
156176

@@ -611,7 +631,7 @@ private String FormatDate(java.util.Date date, String OutputFormat){
611631
* months, years, etc.)
612632
*/
613633
public long compareTo(javaxt.utils.Date date, String units){
614-
return DateDiff(currDate, date.getDate(), units);
634+
return diff(currDate, date.getDate(), units);
615635
}
616636

617637

@@ -623,16 +643,20 @@ public long compareTo(javaxt.utils.Date date, String units){
623643
* months, years, etc.)
624644
*/
625645
public long compareTo(java.util.Date date, String units){
626-
return DateDiff(currDate, date, units);
646+
return diff(currDate, date, units);
627647
}
628648

629649

630650
//**************************************************************************
631-
//** DateDiff
651+
//** diff
632652
//**************************************************************************
633-
/** Implements compareTo public members
653+
/** Used to compare dates. Returns a long value representing the difference
654+
* between the dates for the given unit of measure. Note that this method
655+
* will "round down" differences between dates.
656+
* @param interval Unit of measure. Supports seconds, minutes, hours, days,
657+
* weeks, months, and years.
634658
*/
635-
private long DateDiff(java.util.Date date1, java.util.Date date2, String interval){
659+
private long diff(java.util.Date date1, java.util.Date date2, String interval){
636660

637661
LocalDate s = new Date(date2).getLocalDate();
638662
LocalDate e = new Date(date1).getLocalDate();
@@ -689,62 +713,180 @@ private long DateDiff(java.util.Date date1, java.util.Date date2, String interva
689713
* between two months.
690714
*/
691715
public static double getMonthsBetween(javaxt.utils.Date start, javaxt.utils.Date end) {
716+
return getMonthsBetween(start.getLocalDate(), end.getLocalDate());
717+
}
718+
719+
720+
private static double getMonthsBetween(LocalDate start, LocalDate end) {
721+
722+
723+
//Check if the start date is after the end date. Swap dates as needed
724+
boolean negate = false;
725+
if (start.isAfter(end)){
726+
negate = true;
727+
LocalDate t = start;
728+
start = end;
729+
end = t;
730+
}
731+
732+
733+
//Check if start/end dates fall on the last of the month
734+
boolean startIsLastDayInMonth = start.getDayOfMonth() == start.lengthOfMonth();
735+
boolean endIsLastDayInMonth = end.getDayOfMonth() == end.lengthOfMonth();
692736

693-
LocalDate startLocal = start.getLocalDate();
694-
LocalDate endLocal = end.getLocalDate();
695737

738+
//Calulate months between the 2 dates using Java's built-in ChronoUnit
739+
//Note that the ChronoUnit "rounds down" the interval between the dates.
740+
long m = ChronoUnit.MONTHS.between(start, end);
696741

697-
//When the 2 dates fall on the same day in the month, don't return fractions
698-
int startDay = start.getDay();
699-
int endDay = end.getDay();
742+
743+
//When the 2 dates fall on the same day in the month, and the dates aren't
744+
//on the last day of the month, simply return the value returned by the
745+
//ChronoUnit class
746+
int startDay = start.getDayOfMonth();
747+
int endDay = end.getDayOfMonth();
700748
if (startDay==endDay){
701-
return ChronoUnit.MONTHS.between(startLocal, endLocal);
749+
750+
if (startIsLastDayInMonth && !endIsLastDayInMonth ||
751+
!startIsLastDayInMonth && endIsLastDayInMonth){
752+
//Example: 2024-11-28 2025-02-28
753+
}
754+
else{
755+
return m;
756+
}
702757
}
703758

704759

705-
//When the 2 dates fall on the the last day of the month, don't return fractions
706-
Calendar startCalendar = start.getCalendar();
707-
Calendar endCalendar = end.getCalendar();
708-
if (startCalendar.get(Calendar.DATE) == startCalendar.getActualMaximum(Calendar.DATE) &&
709-
endCalendar.get(Calendar.DATE) == endCalendar.getActualMaximum(Calendar.DATE)){
710-
return ChronoUnit.MONTHS.between(startLocal, endLocal);
760+
//If we're still here, compute fractions
761+
double fraction = 0.0;
762+
if (m==0){
763+
764+
//Simply add up the days between the dates
765+
double numDays = 0.0;
766+
LocalDate d = LocalDate.of(start.getYear(), start.getMonthValue(), start.getDayOfMonth());
767+
while (!d.equals(end)){
768+
769+
if (d.getDayOfMonth() == d.lengthOfMonth()){
770+
fraction += numDays/(double)d.lengthOfMonth();
771+
numDays = 0.0;
772+
}
773+
774+
d = d.plusDays(1);
775+
numDays++;
776+
}
777+
fraction += numDays/(double)d.lengthOfMonth();
778+
711779
}
780+
else{
712781

782+
if (start.getDayOfMonth()>end.lengthOfMonth() || endIsLastDayInMonth){
713783

714-
//If we're still here, return fractional month difference between two dates
715-
return getMonthsBetween(startLocal, endLocal);
716-
}
717784

785+
LocalDate d = LocalDate.of(start.getYear(), start.getMonthValue(), start.getDayOfMonth());
718786

719-
private static double getMonthsBetween(LocalDate start, LocalDate end) {
720787

721-
boolean negate = false;
722-
if (start.isAfter(end)){
723-
negate = true;
724-
LocalDate t = start;
725-
start = end;
726-
end = t;
788+
//Wind back start date (d) a month or two before the end date
789+
m--;
790+
d = addOrSubtractMonths(m, d);
791+
while (d.isAfter(end)){
792+
d = addOrSubtractMonths(-1, d);
793+
m--;
794+
}
795+
796+
797+
//Compute fractions
798+
double numDays = 0.0;
799+
while (!d.equals(end)){
800+
801+
if (d.getDayOfMonth() == d.lengthOfMonth()){
802+
fraction += numDays/(double)d.lengthOfMonth();
803+
numDays = 0.0;
804+
}
805+
806+
d = d.plusDays(1);
807+
numDays++;
808+
}
809+
fraction += numDays/(double)d.lengthOfMonth();
810+
811+
}
812+
else{
813+
814+
815+
//Create new end date using the original end date. Adjust the day
816+
//of the month to match the start date. The new date will be
817+
//either before or after the original end date.
818+
LocalDate e2;
819+
try{
820+
e2 = LocalDate.of(end.getYear(), end.getMonthValue(), start.getDayOfMonth());
821+
}
822+
catch(Exception e){ //leap year exception
823+
e2 = LocalDate.of(end.getYear(), end.getMonthValue(), start.getDayOfMonth()-1);
824+
e.printStackTrace();
825+
}
826+
827+
828+
//Calulate months between the start date and the new end date
829+
m = ChronoUnit.MONTHS.between(start, e2);
830+
831+
832+
if (e2.isAfter(end)){
833+
834+
//subtract from e2
835+
double f = (e2.getDayOfMonth()-end.getDayOfMonth())/(double)end.lengthOfMonth();
836+
fraction -= f;
837+
838+
}
839+
else{
840+
841+
//add from e2
842+
double f = (end.getDayOfMonth()-e2.getDayOfMonth())/(double)end.lengthOfMonth();
843+
fraction += f;
844+
}
845+
}
846+
727847
}
728848

729849

730-
//Compute frantional diff. Read more here: https://stackoverflow.com/a/73947644
731-
LocalDate lastDayOfStartMonth = start.with(TemporalAdjusters.lastDayOfMonth());
732-
LocalDate firstDayOfEndMonth = end.with(TemporalAdjusters.firstDayOfMonth());
733-
double startMonthLength = (double)start.lengthOfMonth();
734-
double endMonthLength = (double)end.lengthOfMonth();
735-
if (lastDayOfStartMonth.isAfter(firstDayOfEndMonth)) { // same month
736-
return ChronoUnit.DAYS.between(start, end) / startMonthLength;
850+
//Add months and fractions
851+
double diff = fraction+(double)m;
852+
853+
854+
//When the 2 dates fall on the the last day of the month, round up
855+
if (startIsLastDayInMonth && endIsLastDayInMonth){
856+
diff = Math.round(diff);
737857
}
738-
long months = ChronoUnit.MONTHS.between(lastDayOfStartMonth, firstDayOfEndMonth);
739-
double startFraction = ChronoUnit.DAYS.between(start, lastDayOfStartMonth.plusDays(1)) / startMonthLength;
740-
double endFraction = ChronoUnit.DAYS.between(firstDayOfEndMonth, end) / endMonthLength;
741-
double diff = months + startFraction + endFraction;
742858

743859

860+
//Return diff
744861
return negate ? -diff : diff;
745862
}
746863

747864

865+
private static LocalDate addOrSubtractMonths(long amount, LocalDate d){
866+
if (amount==0) return d;
867+
868+
if (d.getDayOfMonth() == d.lengthOfMonth()){
869+
d = d.withDayOfMonth(1);
870+
if (amount>0){
871+
d = d.plusMonths(amount);
872+
}
873+
else{
874+
d = d.minusMonths(-amount);
875+
}
876+
d = d.withDayOfMonth(d.lengthOfMonth());
877+
}
878+
else{
879+
if (amount>0){
880+
d = d.plusMonths(amount);
881+
}
882+
else{
883+
d = d.minusMonths(-amount);
884+
}
885+
}
886+
return d;
887+
}
888+
889+
748890
//**************************************************************************
749891
//** isBefore
750892
//**************************************************************************

0 commit comments

Comments
 (0)