Hi, I might have a bit of a weird requirement
I have 2 dates lets say 30th Sept 2019 to 4th April 2022 [User Input]
I want to calculate the days between those 2 dates but I need the days to be broken up by years
Result Should Be:
This is for a calculation which calculates a penalty for late payment (but the % changes each year so need to do the calculation for days in each year and add it up together)
I want to simply enter the dates in 1 record and for it to automatically check the days and calculate and add it up simply giving a single value for penalty
Thanks in advance for any help ๐
ID | Year | Daily Penalty | Days in Year | Yearly Penalty |
1 | 2015 | 8 | 365 | 2920 |
2 | 2016 | 7 | 366 | 2562 |
3 | 2017 | 6 | 365 | 2190 |
4 | 2018 | 5 | 365 | 1825 |
5 | 2019 | 4 | 365 | 1460 |
6 | 2020 | 3 | 366 | 1098 |
7 | 2021 | 2 | 365 | 730 |
8 | 2022 | 1 | 365 | 365 |
Please create a Penalties table as above.
The expression for the total penalty will be then something like
IF(YEAR([Start Date])=YEAR([End Date]),
(HOUR([End Date]-[Start Date])/24+1) * (ANY(SELECT(Penalties[Daily Penalty], [Year]=YEAR([Start Date])))),
(HOUR(DATE("12/31/"&YEAR([Start Date]))-[Start Date])/24+1) * (ANY(SELECT(Penalties[Daily Penalty], [Year]=YEAR([Start Date]))))
+
SUM(SELECT(Penalties[Yearly Penalty], AND([Year]>YEAR([Start Date]), [Year]<YEAR([End Date]))))
+
(HOUR([End Date]-DATE("1/1/"&YEAR([End Date])))/24+1) * (ANY(SELECT(Penalties[Daily Penalty], AND([Year]=YEAR([End Date]), YEAR([Start Date])<YEAR([End Date]))))))
The expression part in magenta calculates the penalty when start and end date are in the same year.
When start and end dates are in different years, the part in green calculates penalties for the start date year, the part in orange for the intermediate years and the part in blue for the end year. Please note the + signs. The entire expression is one expression.
depending on your date locale the part DATE("12/31/"&YEAR([Start Date])) may need to be changed asDATE("31/12/"&YEAR([Start Date]))
Please test well for any edge case.
Thank you very much @Suvrutt_Gurjar This is an amazing reply - I will try this out tomorrow and get back to you
Again thank you very much!
Very good @Suvrutt_Gurjar !!
For the date format, the display would depend on the locale, but in expressions, you are forced to use a US-locale-like mm/dd format. So it should be 12/31 not 31/12.
You can see how much funny this is using the expression assistant, where you write in one locale but get the result in another!
You are very correct @Joseph_Seddik .๐
One of my recent responses was based on just that aspect.
However , in my testing of this response, only variable I could think of for possible failure was that date locale part. Sometimes AppSheet changes the internal processing and I did not want the big expression to fail for that reason,๐ so cautioned @Thilina_Wick
Right!! One should not take such things for granted.
I will keep this in mind @Suvrutt_Gurjar Thank you! ๐
I am posting this to show an alternative approach using a loop mechanism.
I am not sure which is a better approach but I tend to use this approach because expressions tend to be a bit simpler, less table maintenance (at least in this case) although you depend more on actions and a helper col used as a temporary value.
As someone mentioned, there definitely are more than one way to do the same.
I guess you probably need to know to use both approaches depending on the situation.
Here's how you do it.
Create a pen_rate table: Year (key), rate (daily rate)
Create a temporary col [cur_year] to hold the year to use in each iteration of the loop. This should be initialized to YEAR([fr_date])
Four actions.
1) Calculate the yearly penalty. This one calculates the year by year penalty and accumulates the result into the col [penalty]
[penalty] +
IFS(
[cur_year]=year([fr_date]), HOUR(date("12/31/" & [cur_year]) - [fr_date])/24 + 1,
[cur_year]=year([to_date]), HOUR([to_date] - date("01/01/" & [cur_year]))/24 + 1,
TRUE, HOUR(date("12/31/" & [cur_year]) - date("01/01/" & [cur_year]))/24 + 1
) * LOOKUP([cur_year],"pen_rate","year","rate")
Somewhat similar to @Suvrutt_Gurjar 's case determination logic.
2) Count up the [cur_year]. Simply
[cur_year] + 1
3) Repeat step. This calls the 4) if
[cur_year] <= year([to_date])
4) An action to execute the sequence of the above three
A few screen shots. Note, [memo] just writes out the result of each iteration. not required for production.
Hope this adds an additional tool in your tool box!
Thank you very much @TeeSee1 Yes this post has definitely been an enlightening experience I've learned a lot for a relative beginner who learnt most of appsheet development through these forum posts ๐
Hi @TeeSee1 ,
If we may have further discussion on this interesting requirement. Using actions for iterative calculations is definitely an innovative way and shows your technical prowess for crafting different solutions. Kudos to you.
However I believe using looping with actions is best used in scenarios like adding multiple new rows etc. I am unsure about using these for iterative calculations-in this case year wise calculations. Some points to ponder
1. Each change in start or end date will need invoking of at least 4 actions + 1 action for each year. If the user quickly checks 10 different cases, around 50 actions could pile up. Of course they will execute in background but there could be too many actions involved in general on a daily basis.
2. There will need to be a direct ( such as tapping on group action icon button) or indirect way ( form save event) to invoke actions after the user changes the dates. So there are additional user interactions.
3. If dynamic calculation of penalties with respect to TODAY() is required, it will not happen on its own for multiple records.
With the pure expression method
1. If the expression is in a VC , the changed penalty ( it it is calculated with respect to TODAY()) will auto reflect daily for all applicable records.
2. Even if the user checks 100's of different start /end date combinations in a form , the penalties will immediately calculate without even saving the form- something like a calculator.
May be I missed some point in actions method. Please feel free to add your thoughts.
@Suvrutt_Gurjar thank you for your comment.
I agree with most of your points.
One thing I do not agree is that this loop mechanism is not innovative, it's been documented by Steve and I think this is a technique that everyone should know if you want to maximize the power of the platform.
And to be honest, I personally find this easier to implement than coming up with a complex expression...
Having said that considering all your other points, your approach probably is the better one for this particular use case. There was a similar case posted not too long ago which @Joseph_Seddik also provided a solution based on an expression and a year table vs my looping one. I have been wondering what criteria to use to choose an approach between the two and your post has made it a lot clearer.
Thanks again, and despite a lot of things that could be improved, AppSheet is a great tool and I enjoy working on it.
Cheers!
Hi @TeeSee1 ,
Thank you for your revert. Good that you found the comments useful and mostly agree. Intention was to only reader know the pros and cons.
You mentioned
Here, I politely mention and sorry to say that you have totally misquoted me. I sincerely request you not to do so. I have never said iterative approach is not innovative. In fact I started by saying that it is innovative way, but I believe it is not suited for this case.
I mentioned:
My only point was about using looping for mass calculations is not a fit case and other challenges that I pointed out.
Also I typically never respond on alternative approaches unless it is totally going in wrong direction, but you mentioned certain advantages over expression way and so felt compelled to elaborate for future reader.
I did not intend to say you said the looping was not innovative.
My disagreement was although you said looping was innovative, I do not think it is innovative.๐
As you have so rightly pointed out it should be used to create multiple rows from a single source, for instance and the technique is fairly well known already.
So I do not take any offence in any way. I always welcome any learning opportunities!
Dear @TeeSee1
>> I have been wondering what criteria to use to choose an approach between the two and your post has made it a lot clearer.
I believe personal preference plays a significant part. For me, expressions feel more natural, also especially in a form, I much appreciate the fact that calculation results are shown immediately before saving the form then waiting for the looping action to terminate.
From a more objective view, looping actions is a creative workaround for the lack of iteration expressions in AppSheet. For me this is their only raison-d'รชtre as they say. As @Suvrutt_Gurjar said they provide the solution for unsolvable problems with expressions like adding multiple rows, and for me this is where they should be used.
Additionally, it is generally a good practice especially in a centralized-server-based system over the internet (in contrast to over LAN), to 1. minimize back and forth communications, and 2. minimize database query and updates. So where an expression could perform a complex calculation then perform one database update, a recurrent action would on the other hand perform 2*n, 4*n, m*n times database access operations for the same tasks. Even when you have a stand-alone device, here cpu runs, memory usage and battery consumption comes into play.
Furthermore, for those problems that wouldn't be solved by AppSheet expressions, I would rather call a js function from the sheet rather than using a looping action. Here for example, @Koichi_Tsuji used looping actions to split a string into a list of characters, where I used js in the sheet for the same purpose.
Just my humble contribution to the subject.
Yes, the load on the backend infrastructure was another concern for me with looping.
And also in the case of processing row by row vs batch update the latter is definitely is the better practice especially in a client server environment.
As for JS, I am hesitant to suggest this in this forum in the spirit of NO CODING, although I would not mind using it myself. So as long as looping is required to process data in not so UNCOMMON use cases, I wish more straight-forward way to do so would be provided.
Meanwhile I have to brush up my expression skills further and for this I do learn from both ( @Suvrutt_Gurjar @Joseph_Seddik ) of your solutions.
Thanks!
@TeeSee1 On the contrary my friend, I learn from you a lot!
I'd like just to add a little comment regarding this:
>> Yes, the load on the backend infrastructure was another concern for me with looping.
An application server would typically protect itself by limiting the amount of memory and CPU processing power that would be allocated to every socket/process, so it is not what we'd typically worry about. Rather, it is because of this limitation, your requests would be queued and might even be subject to temporary penalization by the server in case of repeated similar behavior.
Unless the looping actions are launched by a bot, they will be run I believe locally on the device. However, there will be one database update per iteration, so if the action loops 100 times, we'll have 100 data updates.
Hi @Joseph_Seddik , you have so eloquently explained. I am totally on same page as you.
@Suvrutt_Gurjar @TeeSee1 @Joseph_Seddik Each of you is a true asset to this community! This thread makes me smile!
User | Count |
---|---|
14 | |
11 | |
9 | |
7 | |
4 |