@@ -104,7 +104,7 @@ public class Date implements Comparable {
104
104
//**************************************************************************
105
105
//** Constructor
106
106
//**************************************************************************
107
- /** Creates a new instance of date using current time stamp
107
+ /** Creates a new instance of this class using the current time
108
108
*/
109
109
public Date (){
110
110
currDate = new java .util .Date ();
@@ -114,7 +114,7 @@ public Date(){
114
114
//**************************************************************************
115
115
//** Constructor
116
116
//**************************************************************************
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
118
118
*/
119
119
public Date (java .util .Date date ){
120
120
if (date ==null ) throw new IllegalArgumentException ("Date is null." );
@@ -125,7 +125,7 @@ public Date(java.util.Date date){
125
125
//**************************************************************************
126
126
//** Constructor
127
127
//**************************************************************************
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
129
129
*/
130
130
public Date (Calendar calendar ){
131
131
if (calendar ==null ) throw new IllegalArgumentException ("Calendar is null." );
@@ -137,7 +137,26 @@ public Date(Calendar calendar){
137
137
//**************************************************************************
138
138
//** Constructor
139
139
//**************************************************************************
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)
141
160
* since 1/1/1970.
142
161
*/
143
162
public Date (long milliseconds ){
@@ -150,7 +169,8 @@ public Date(long milliseconds){
150
169
//**************************************************************************
151
170
//** Constructor
152
171
//**************************************************************************
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.
154
174
*/
155
175
public Date (String date ) throws ParseException {
156
176
@@ -611,7 +631,7 @@ private String FormatDate(java.util.Date date, String OutputFormat){
611
631
* months, years, etc.)
612
632
*/
613
633
public long compareTo (javaxt .utils .Date date , String units ){
614
- return DateDiff (currDate , date .getDate (), units );
634
+ return diff (currDate , date .getDate (), units );
615
635
}
616
636
617
637
@@ -623,16 +643,20 @@ public long compareTo(javaxt.utils.Date date, String units){
623
643
* months, years, etc.)
624
644
*/
625
645
public long compareTo (java .util .Date date , String units ){
626
- return DateDiff (currDate , date , units );
646
+ return diff (currDate , date , units );
627
647
}
628
648
629
649
630
650
//**************************************************************************
631
- //** DateDiff
651
+ //** diff
632
652
//**************************************************************************
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.
634
658
*/
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 ){
636
660
637
661
LocalDate s = new Date (date2 ).getLocalDate ();
638
662
LocalDate e = new Date (date1 ).getLocalDate ();
@@ -689,62 +713,180 @@ private long DateDiff(java.util.Date date1, java.util.Date date2, String interva
689
713
* between two months.
690
714
*/
691
715
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 ();
692
736
693
- LocalDate startLocal = start .getLocalDate ();
694
- LocalDate endLocal = end .getLocalDate ();
695
737
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 );
696
741
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 ();
700
748
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
+ }
702
757
}
703
758
704
759
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
+
711
779
}
780
+ else {
712
781
782
+ if (start .getDayOfMonth ()>end .lengthOfMonth () || endIsLastDayInMonth ){
713
783
714
- //If we're still here, return fractional month difference between two dates
715
- return getMonthsBetween (startLocal , endLocal );
716
- }
717
784
785
+ LocalDate d = LocalDate .of (start .getYear (), start .getMonthValue (), start .getDayOfMonth ());
718
786
719
- private static double getMonthsBetween (LocalDate start , LocalDate end ) {
720
787
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
+
727
847
}
728
848
729
849
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 ) ;
737
857
}
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 ;
742
858
743
859
860
+ //Return diff
744
861
return negate ? -diff : diff ;
745
862
}
746
863
747
864
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
+
748
890
//**************************************************************************
749
891
//** isBefore
750
892
//**************************************************************************
0 commit comments