Consistent union of months into a quarter, half year, year - java

I have an array of Deposits (first column)
data class Deposit(val term: Int, val termType String, val rate BigDecimal)
I tried to divide the array into blocks of 3 elements in order to combine 1 month into one quarter (assuming the same rate), but in other cases it is more convenient to take 2 elements, although in this case the union may not occur correctly. 2nd and 3rd quarter are merged into half a year.
Tell me how you can, albeit not in an optimal way, recursively perform a sequential conversion of months into a quarter, half a year and a year.
Example:
Input
Step 1..N
Output
1 month – 6%
1 quarter — 6%
1 quarter — 6%
1 month – 6%
1 month – 6%
3 month – 5%
1 quarter – 5%
1 quarter – 5%
3 month – 5%
1 quarter – 5%
1 quarter – 5%
3 month – 3%
1 quarter – 3%
1 quarter – 3%
3 month – 6%
1 half a year – 6%
1 year – 6%
3 month – 6%
3 month – 6%
1 half a year – 6%
3 month – 6%
3 month – 7%
1 half a year – 7%
1 half a year – 7%
3 month – 7%
3 month – 8%
1 half a year – 8%
1 half a year – 8%
3 month – 8%
I tried to divide the array into blocks of 3 elements in order to combine 1 month into one quarter (assuming the same rate), but in other cases it is more convenient to take 2 elements, although in this case the union may not occur correctly. 2nd and 3rd quarter are merged into half a year.

Related

Round instant with more than 6 digits (nanoseconds) after comma

I have postgresql with TIMESTAMP field, which have Instant with date and time. For example: 2021-10-13T15:04:24.944921Z.
As you can see, there are 6 digits after comma - 944921. But what if I have more digits, for example: 2021-10-13T07:14:47.791616921Z. How can I correctly round such Instant to 2021-10-13T07:14:47.791617Z?
The code is short but requires a bit of explanation. You are right, the best we have is the truncatedTo method, and it always rounds down. Instead we want the half-up rounding that we learned in school: If the 7th decimal is 4 or less, round down. If it is 5 or higher, round up.
To obtain this I first add 500 nanoseconds. Then truncate. If the 7th decimal was 4 or less, it is still 9 or less, so the digits before it have not been changed, and truncation will do the rounding-down that we want. If the 7th decimal was 5 or more, adding 500 nanoseconds will overflow it, the 6th decimal will be incremented by 1, and the 7th decimal will end up in the 0 through 4 range. Then truncation will effectively do the rounding up of the original value that we wanted.
Instant sourceValue = Instant.parse("2021-10-13T07:14:47.791616921Z");
Instant rounded = sourceValue.plusNanos(500).truncatedTo(ChronoUnit.MICROS);
System.out.println(rounded);
Output is the desired:
2021-10-13T07:14:47.791617Z
Let’s try the edge cases too.
Instant sourceValue = Instant.parse("2021-10-13T07:14:00.000000499Z");
2021-10-13T07:14:00Z
Instant sourceValue = Instant.parse("2021-10-13T07:14:59.999999500Z");
2021-10-13T07:15:00Z

How To Calculate Month From The Total Number Of Days Difference In Hebrew Calendar

I am using this to calculate the number of days difference in hebrew calendar and it returns correct days difference. Now I want to convert those days in the months or years.
But the problem is in the Hebrew calendar
A year in the Hebrew calendar can be 353, 354, 355, 383, 384, or 385
days long. Regular common years have 12 months with a total of 354
days. Leap years have 13 months and are 384 days long. Months with
uneven numbers usually have 30 days, while months with even numbers
have 29 days.
I have tried to divide the days with total days in a year but it has a different total number of days in the year. Can you please me an idea of how to achieve this?
Looks like a loop that keeps subtracting is the easiest to be understood. I guess you know the year at either end of the difference. Then a loop like this could work:
int yearBegin = 1234;
int daysDiff = calcDaysDiff(); // your existing routine
int yearAnalyzing = yearBegin;
int years = 0;
while (daysDiff > 0) {
int daysInThatYear = calcDaysInYear (yearAnalyzing); // you calc accdg your rules
while (daysDiff >= daysInThatYear) {
++years;
daysDiff -= daysinThatYear;
yearAnalyzing++; // or --, depending which direction you want to go
}
// less than a year is left
// calculate the months (should be easy)
}
If your beginning date starts in the middle of the year you would have to work these days off your difference first, before you start the loop.
You can use my lib Time4J which offers the feature to determine differences measured in years, months or days. Example (from a JUnit test case):
HebrewCalendar start = HebrewCalendar.of(5778, HebrewMonth.HESHVAN, 6);
HebrewCalendar end = HebrewCalendar.of(5778, HebrewMonth.ELUL, 6);
assertThat(HebrewCalendar.Unit.MONTHS.between(start, end), is(10));
start = start.plus(CalendarDays.ONE);
assertThat(HebrewCalendar.Unit.MONTHS.between(start, end), is(9));
start = start.minus(3, HebrewCalendar.Unit.YEARS);
assertThat(HebrewCalendar.Unit.YEARS.between(start, end), is(3));
start = start.plus(6, HebrewCalendar.Unit.YEARS).minus(CalendarDays.of(2)); // AM-5781-HESHVAN-5
assertThat(HebrewCalendar.Unit.YEARS.between(start, end), is(-2));
start = start.with(HebrewCalendar.MONTH_OF_YEAR, HebrewMonth.ELUL); // AM-5781-ELUL-5
assertThat(HebrewCalendar.Unit.MONTHS.between(start, end), is(-36));
start = start.plus(CalendarDays.ONE);
assertThat(HebrewCalendar.Unit.MONTHS.between(start, end), is(-37));
start = start.minus(37, HebrewCalendar.Unit.MONTHS);
assertThat(start, is(end));
The logic is somehow similar to the standard logic in the java.time-package. When the day-of-month of end date is smaller than that of start date, then the calculated month delta (or year delta) will be decreased by one.
Generally I cannot recommend to try to convert days into months or years applying a fixed factor because the Hebrew calendar has sometimes 12, sometimes 13 months. My lib does not try it, too, but applies an optimized kind of counting. It is also impossible to convert a day delta to a month delta without any further informations about start or end date for principal reasons:
Imagine the SAME day delta had been determined with a start date
short before Adar I (leap month) or with another month later. Then the month
delta will be different because the second case does not include a leap month!
More examples how to handle the Hebrew calendar can be seen in the API online.
If you are on Android then please use the specialized derivate Time4A with similar API.

Converting time4j Duration<IsoUnit> from P365D to P1Y and so on

Instant i1 = Instant.now();
Instant i2 = Instant.now().plusSeconds(60 * 60 * 24 * 365);
Duration<IsoUnit> dur = Duration.from((TemporalAmount) java.time.Duration.between(i1, i2));
System.out.println(dur);
this code prints P365D, is there a way to make units fill up the next bigger unit, so this becomes P1Y and if i had like P334D to P11M and so on?
time4j version is 4.38
The other comments and answers are completely right to say that a year is not always equal to 365 days.
The Time4J-method net.time4j.Duration.from(TemporalAmount) returns a normalized duration using STD_PERIOD as normalizer. The documentation of this normalizer says:
Normalizes the duration items on the base of 1 year = 12 months and 1
day = 24 hours and 1 hour = 60 minutes and 1 minute = 60 seconds -
without converting days to months.
So you can only expect the result to be in days when you start with a temporal amount defined in seconds.
If you still want a year then I suggest you to first convert your instants to calendar dates using an appropriate time zone. But in leap years, your code would still produce 365 days and not a year after having added 60 * 60 * 24 * 365 seconds to the second instant. So your addition of seconds is also flawed because it is based on false assumptions.
Side note:
If you want the reverse way, namely how many seconds are in a year then you might use code like
Moment m1 = Moment.nowInSystemTime();
Moment m2 = m1.toZonalTimestamp(ZonalOffset.UTC).plus(1, CalendarUnit.YEARS).atUTC();
long seconds = SI.SECONDS.between(m1, m2); // = 366 days in seconds if applied on date 2019-05-22!
With a future version of Time4J and possible leap second at the end of year 2019, the code might even produce an extra second.
Anyway, I advise you to update Time4J to v5.4 and consider following mappings:
java.time.Instant as input => net.time4j.MachineTime.from(...)
java.time.LocalDateTime/net.time4j.PlainTimestamp => net.time4j.Duration.from(...)
So if you really want years as possible output in printing durations and you have instants/moments then first convert to LocalDateTime/PlainTimestamp (using a time zone or offset) before you create the appropriate duration object.
Update from 2019-05-25:
Another way with the version v5.4 (or later) is possible via "fuzzy" durations. You could normalize the durations you get by applying an approximation. Example:
Duration<IsoUnit> d = Duration.of(60 * 60 * 24 * 365, ClockUnit.SECONDS);
d = d.with(Duration.approximateMaxUnitOnly());
System.out.println(d); // P1Y
Due to the nature of this kind of normalization, you cannot expect exact results.
A year doesn't have a fixed amount of seconds:
Leap years have an additional day on February 29
Leap seconds are sometimes added
Due to above you have to know how many seconds are in a year. It seems like instead of Instant and Duration you should be using LocalDate and Period (at least when using java.time):
LocalDate d1 = LocalDate.now();
LocalDate d2 = d1.plusYears(1);
Period p = Period.between(d1, d2);
System.out.println(p); // P1Y

JodaTime round Duration/Period to nearest minutes/hours/days/weeks/months/years

If I have two instants in JodaTime, for example
DateTime then=new DateTime(0);
DateTime now=DateTime.now();
and I want to get the number of years between these two dates, I can do
new Period(then,now).getYears();
or
Years.yearsBetween(then,now);
But the problem is that if the interval is not an exact number of years, both of these methods will return the floor of the number of years. So for example, of you have two instants which are two years and 11 months apart, it will still return "0" years.
Is there any way JodaTime provides to get the rounded number of years,months,etc?
The purpose of this is that I wish to display durations in a human friendly format, for example
1 year, 11 months, 30 days, 1 hour -> 2 years.
6 days 4 hours 1 minute -> 1 week
4 hours 18 minutes 5 seconds -> 4 hours
And so I'd like to display properly rounded amounts.
I think I could probably check whether the end of the interval is closer to N Units in the future or N+1 Units, but is there a better way to do this?

Do Java system milliseconds take account of leap seconds?

The java function System.currentTimeMillis() apparently returns the number of seconds since 1st January 1970. However, according to wikipedia.org/wiki/Leap_second, since 1972 there have been 25 leap seconds. This means the actual number of seconds since 1st January 1970 has been 25 more than a naive calculation would suggest. Does System.currentTimeMillis() do the naive calculation and ignore the leap seconds?
Officially, it's up to the OS and implementation - at least for Date. From the docs of java.util.Date:
Although the Date class is intended to reflect coordinated universal time (UTC), it may not do so exactly, depending on the host environment of the Java Virtual Machine. Nearly all modern operating systems assume that 1 day = 24 × 60 × 60 = 86400 seconds in all cases. In UTC, however, about once every year or two there is an extra second, called a "leap second." The leap second is always added as the last second of the day, and always on December 31 or June 30. For example, the last minute of the year 1995 was 61 seconds long, thanks to an added leap second. Most computer clocks are not accurate enough to be able to reflect the leap-second distinction.
I suspect you'll find that although your computer clock is roughly aligned to UTC, that's done via NTP or the like correcting the clock periodically, rather than the OS really implementing leap seconds.
I believe the JRE libraries typically do assume the 86400-second day. It makes life so much simpler, and if you're going to correct for an inaccurate system clock anyway, you might as well correct for leap seconds that way too.
You really want to work out what you're interested in. If you need a way of representing dates and times which use leap seconds, the standard Java libraries may not work well for you. Even JSR-310 no longer supports leap seconds as far as I can tell (which is a very sensible decision for most developers).
POSIX requires that the system clock not admit the existence of leap seconds. MS Windows cannot guarantee the quality (nor existence) of the system clock hardware, and it has eschewed guarantee of 1-second accuracy. Java cannot easily do anything that the underlying system refuses to do. The operating systems are hamstrung by the history of the international regulations that result in one IEEE standard (PTP) that requires leap seconds and another (POSIX) that denies them.
One easy way to check if leap seconds are accounted for or not is to compute the number of seconds elapsed since the Epoch for 00:00 on any given day in the current year.
If that number of seconds is congruent to 00 modulo 60, then the leap seconds are not accounted for as in 2013 you should have a modulus of 25 (to account for the past 25 leap seconds).
I did a small experiment on javarepl:
java> new Date(1000L * 86400 * (365 * 4 + 1) * 12)
java.util.Date res0 = Mon Jan 01 00:00:00 UTC 2018
As you can see, a simple "naive" arithmetics that just regards leap year, but not leap seconds, is used. No extra seconds are added or subtracted.
Update:
Same for the new Instant class:
java> Instant.ofEpochMilli(1000L * 86400 * (365 * 4 + 1) * 12)
java.time.Instant res0 = 2018-01-01T00:00:00Z
Looking at the Javadoc for currentTimeMillis(), it referes to the documentation of the Date class, which has this to say:
Although the Date class is intended to reflect coordinated universal time (UTC), it may not do so exactly, depending on the host environment of the Java Virtual Machine. Nearly all modern operating systems assume that 1 day = 24 × 60 × 60 = 86400 seconds in all cases. In UTC, however, about once every year or two there is an extra second, called a "leap second." The leap second is always added as the last second of the day, and always on December 31 or June 30. For example, the last minute of the year 1995 was 61 seconds long, thanks to an added leap second. Most computer clocks are not accurate enough to be able to reflect the leap-second distinction.
So to answer your question: Yes, leap seconds are accounted for.

Categories