@@ -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