How to implement customized spreadsheet formula fields into java? - java

I have a requirement where I need to get values based on some excel formula fields. On a high level, we have an excel where user has the liberty to enter date values and based on these input the formula fields are updated and total revenue field value is calculated.
Now to perform these validations via code will it be better to
Write own utilities in Java for each formula in spreadsheet.
Use Apache POI to input the values in spreadsheet on runtime and evaluate formulas to get revenue value.
On approach 1 I am having below issues :
On implementing below code for =YEARFRAC(B1, B2+1)*12 formula
public static double calculateContractLength(String startdate,String enddate) {
double monthsBetween = ChronoUnit.MONTHS.between(
LocalDate.parse(startdate),
LocalDate.parse(enddate));
return monthsBetween;
}
I see mismatch in result of excel formula and above code. For Instance Start Date = 2/12/2020 End Date = 12/31/2022, I see excel showing 34.63 but Code returning 34.0
To better handle is I want to know if there are some existing libraries which have already implemented these spreadsheet formulas or just updating the excel at run time with Apache POI would be a better solution.

I tend to understand from the documentation of the yearfrac function (link at the bottom) that when given two arguments (as in your example), it will count 30 days in a month. I don’t know whether it’s a requirement that your Java code does exactly the same. In any case here’s my attempt to mimic:
public static double calculateContractLength(String startdate,String enddate) {
Period per = Period.between(
LocalDate.parse(startdate),
LocalDate.parse(enddate));
return per.toTotalMonths() + per.getDays() / 30.0;
}
Trying it out with the example dates from your question, expecting 34.63:
System.out.println(calculateContractLength("2020-02-12", "2022-12-31"));
Output is:
34.63333333333333
I also tried the dates from the example in the yearfrac documentation:
System.out.println(calculateContractLength("2012-01-01", "2012-07-30"));
The documentation says that the result is 0.58055556 years, so that should be 0.58055556 * 12 months = 6.96666672 months. We get:
6.966666666666667
There will probably still be (a bit greater) differences in corner cases. Fractions of months are not well-defined. One sign if the same is that the MS documentation states:
The YEARFRAC function may return an incorrect result when using
the US (NASD) 30/360 basis, and the start_date is the last day in
February.
Link
Documentation of the YEARFRAC function

Related

How do I format a JSR-385 Quantity with fixed number of decimal digits?

I'm trying to convert hard-coded formatting to a Java Units API implementation.
The existing code outputs (for this example, the temperature value in degrees) with two decimal places. For example, 38.70°C. While I'd like to allow the user to specify their own formatting code (which is the end-goal of the change), I think it would be useful to keep the legacy behavior to give people a chance to migrate.
The existing code looks like:
return String.format("%.2f\u00B0C", this.temperature);
The code I'm trying to use looks like:
DecimalFormat numberFormat = (DecimalFormat) DecimalFormat.getInstance();
numberFormat.setMinimumFractionDigits(2);
numberFormat.setMaximumFractionDigits(2);
NumberDelimiterQuantityFormat formatter =
NumberDelimiterQuantityFormat.builder()
.setNumberFormat(numberFormat)
.setDelimiter("")
.setUnitFormat(SimpleUnitFormat.getInstance())
.build();
return formatter.format(temperature);
It does format, but not with the specified precision. I'd expect 38.70°C but instead get 38.70000076293945℃.
If I just do
numberFormat.format(temperature.getValue().floatValue());
then it does format correctly ("38.70"). So I think the DecimalFormat is basically OK.
I considered just manually building my formatting. However that doesn't really work for what I want to do - pass in the NumberDelimiterQuantityFormat (or applicable interface).
Can anyone suggest an appropriate way to format a Quantity<> with fixed decimal precision?
First off, I'm completely unfamiliar with the Java Unit API and this implementation, but this seemed like an interesting question, so I looked into it.
I had a look at the implementation of NumberDelimiterQuantityFormat and right there in the implementation of the format method it modifies the maxiumFractionDigits of the NumberFormat depending on the fraction
if (quantity != null && quantity.getValue() != null) {
fract = getFractionDigitsCount(quantity.getValue().doubleValue());
}
if (fract > 1) {
numberFormat.setMaximumFractionDigits(fract + 1);
}
Source
This makes little sense to me for two reasons:
It negates the whole reason to have a NumberFormat in the first place especially in context with floating point numbers where it's virtually impossible to avoid superfluous fraction digits.
It modifies the internal state of the NumberDelimiterQuantityFormat in a method where it isn't expected.
I should have checked first, but there is actually an issue about this, which is "being analyzed" for several months now. Maybe it would make sense to ask in there.

How to manipulate Ranges of Time of a Day

I'm working with an agenda in Java. I have stored in my database the day of the week, the start and end time of some labs availability.
Now I need to provide a service for a schedule system by showing only the unavailable times of the day. For example, if day one has start time 13:00 and end time 19:00, I need to return a range just like this:
[00:00 - 13:00, 19:00 - 23:59] . Remembering that a day can have more than a range available.
Is there any Java Class or API that could help me on subtracting these ranges?
My lib Time4J offers following solution for the subtraction problem:
ClockInterval fullDay = ClockInterval.between(PlainTime.of(0), PlainTime.of(24));
ClockInterval slot = ClockInterval.between(PlainTime.of(13, 0), PlainTime.of(19, 0));
IntervalCollection<PlainTime> icoll = IntervalCollection.onClockAxis().plus(fullDay);
List<ChronoInterval<PlainTime>> result = icoll.minus(slot).getIntervals();
The resulting list of half-open intervals (with open end) can be easily iterated through and gives the expected result {[T00:00/T13:00), [T19:00/T24:00)}. Every result interval can be converted to a standard ClockInterval, too. There are also various methods to print such intervals in a localized way. Furthermore, you might find the class DayPartitionBuilder interesting which allows to connect weekdays and time schedules in streaming, see the example given in the documentation.
About compatibility with java.time:
The between()-methods of ClockInterval also accept instances of java.time.LocalTime.
Every instance of PlainTime can be converted back to LocalTime by help of the method toTemporalAccessor() with the exception of the value 24:00 which exists in Time4J but not in java.time.LocalTime.

java.time.Year#range does not return a concrete week of year (Java 1.8)

I'm wondering why a concrete instance of java.time.Year#range fails to return a concrete ValueRange of possible week numbers.
I know that the following code fails, because IsoFields.WEEK_OF_WEEK_BASED_YEAR is not supported:
ValueRange range = Year.of(2018).range(IsoFields.WEEK_OF_WEEK_BASED_YEAR)
Nevertheless, I am wondering why the following code returns a ValueRange of 0/1 - 52/54:
ValueRange range = Year.of(2018).range(WeekFields.ISO.weekOfWeekBasedYear())
I know that this is true, but it is a generic ValueRange true of any Year.
From what I gather, given a concrete instance of a year, you are able to find out the actual range of numbers of weeks that fit into this particular year (in this example Year.of(2018) contains weeks 1 - 52 (since 2018-12-31 actually belongs to 2019-W01.
Same rant basically applies to YearMonth.
The only way I know to get the proper range is with the following:
LocalDate date = Year.of(2021).atDay(4)
ValueRange range2 = date.range(WeekFields.ISO.weekOfWeekBasedYear()) // 1-52
since --01-04 is the one that always belongs to --W01, so you have to beware that the following will actually yield different result:
LocalDate date = Year.of(2021).atDay(1)
ValueRange range = date.range(WeekFields.ISO.weekOfWeekBasedYear()) // 1-53 (incorrect!)
Still, it seems somehow wrong to create some arbitrary date just for the reason of getting the value range of possible week numbers in a given year.
So, basically, what I would like to ask is what is the reason for this and if you know any better ways to do this?
Also, I haven't had a change to try this in Java versions other than 1.8; perhaps this was improved in JDK9/10/11?

Parse any string to Sql date

I wonder if it's possible to parse any string (at least to try) to sql Date without specifing the string format? In other words I want to make a generic method who take as input a string and return an sql Date.
For instance I have:
String date1="31/12/2099";
String date2="31-12-2099";
and call parseToSqlDate(date1) and parseToSqlDate(date2) which will returns sql dates.
Short answer: No
Why: Parsing any string to a valid date is a task you as an intelligent being could not do (there is no "logical" way to determine the correct date), so you cannot "tell" a computer(program) to do that for you (see JGrice's comment, and there we still have 4-digit years).
Long answer: Maybe, if you are willed to either take risks or do not need a high rate of success.
How:
Define your minimal (format) requirements of a date. E.g. "a minimal date contains 1-8 numbers; 01/01/2001 , 01-01-01 , 01.01 (+current year) , 1.1 (+current year), 1 (+current month + current year) and/or "..contains 1-6 numbers and the letters for months"; 01-Jan-2001 and so on.
Split the input along any non-number/non-month-name characters, with a regex like [^0-9a-zA-Z] (quick thought, may hold some pitfalls)
You now have 1 to 3 (actually more if e.g. the time is included) separate numbers + 1 month name which can be aligned for year/month/day any way you like
For this "alignment", there are several possibilities:
Try a fixed format at first, if it "fits", take it, else try another (or fail)
(only of you get more than one entry at a time) guess the format by assuming all entries have the same (e.g. any number block containing values > 12 is not a month and > 31 is not a day)
BUT, and this is a big one, you can expect any such method to become a major PITA at some point, because you can never fully "trust" it to guess correctly (you can never be sure to have missed some special format or introduced some ambiguous interpretation). I outlined some cases/format, but definitely not all of them, so you will refine that method very often if you actually use it.
Appendix to your comment: "May be to add another parameter and in this way to know where goes day , month and so on?" So you are willed to add "pseudo-format-string" parameter specifying the order of day, month and year; that would make it a lot easier (as "simply" filtering out the delimiters can be achieved).

Calculating t-inverse

I'm trying to calculate a the inverse of a 2 tailed Student Distribution using commons-math. I'm using Excel to compare values and validate if my results are correct.
So Using excel to calculate TINV with 5 degrees of freedom and 95.45% I use
=TINV(0.0455,5)
And get the Result: 2.64865
Using commons Math like so :
TDistribution t = new TDistribution(5);
double value = t.inverseCumulativeProbability(0.9545);
I get Result : 2.08913
I'm probably doing something wrong obviously. I'm not really that math savvy but I need to port an Excel sheet formula to Java for a project and got stuck on this.
What should I be using to get the result exactly like the TINV value? What am I missing.
MS documentation [1] says that TINV returns a two-tailed value. I'm pretty sure Commons Math is returning a one-tailed value. In order to get Commons Math to agree with Excel, cut the tail mass in half, i.e., call
t.inverseCumulativeProbability (1 - tail_mass/2);
[1] http://office.microsoft.com/en-us/excel-help/tinv-function-HP010335663.aspx

Categories