7 ms·
What is 2021-02-28 + 1 month? Is that 2021-03-28 or 2021-03-31? It is far from obvious what a normal human would do most of the time here. The lack of associat
by FooBarWidget 5y ago
What is 2021-02-28 + 1 month? Is that 2021-03-28 or 2021-03-31? It is far from obvious what a normal human would do most of the time here.
The lack of associativity is still a problem. If 2021-02-28 + 1 month = 2021-03-28,then (2021-02-28 + 1 month) + 1 month = 2021-03-28 + 1 month = 2021-04-28. While if I ask what is 2021-02-28 + 2 months (given 2021-02-28 + 1 month = 2021-03-31), most people would say 2021-04-30.
While I am not entirely sold on using awkward/"honest" names by default, the author does raise a good point: sometimes concepts are inherently messy or full of important edge cases, and we shouldn't just brush that aside.
I recently ran into a similar problem. I am implementing a distributed lock (https://www.joyfulbikeshedding.com/blog/2021-05-19-robust-distributed-locking-algorithm-based-on-google-cloud-storage.html https://www.joyfulbikeshedding.com/blog/2021-05-19-robust-di...) -- like a mutex, but works across processes and machines. I try to mimic the language's standard Mutex API as much as possible.
A normal Mutex has a query method named "owned?" to check whether the calling thread owns the mutex. When I tried implementing this method for my distributed lock, it raised a question: owned according to who? Owned according to the local state that represents the lock, or according to the state that lives in the server? Because they can differ (e.g. due to bugs in other clients or because an admin manually messed with the state). So I opted for "honest names" here too and implemented two methods: "owned_according_to_local_state?" and "owned_according_to_server_state?"
- taeric 5y agoI think if you try and confuse folks with this, that is easy to do. But, this isn't special. What happens if you add a meter to a kilometer? For most measurements, you get a kilometer. We teach this with significant digits, but then typically ignore it and assume all sorts of conventions that are not spelled out. If it is important in the domain you are working in, take extra care to understand the maths that you are built on. And don't be surprised to find special cases everywhere.
- mjevans 5y agoYou seem to be stuck on: 'what is the value of 1 month' and thinking in terms of days, because that appears to be the precision of the left value. Programmers exist in a world where things such as leap seconds matter. Normally if you have a timestamp that is just before a leap second, then add exactly a day's worth of seconds, you'd slide back a little in time. This might matter in another context, such as defining the limits of neighboring ranges properly. Also, who's to say the underlying precision is a second? The intent of the library in question is to behave the way most people would. With imperfect buckets and idealized answers; yet also precision where someone makes the attempt to be specific. None of the examples in the article use a more vague syntax, such as "0 days before the end of the month". They start with what a human might, a rounded but full date; then apply an interval. So a more clear contrived example might be. Jan 31 .plus(1 months) => Feb 28 Jan 31 .plus(2 months) => Mar 31 Jan 31 .plus(3 months) => Apr 30 Jan 31 .plus(4 months) => May 31 Jan 1 .plus(1 months) => Feb 1 Jan 1 .plus(2 months) => Mar 1 Jan 1 .plus(3 months) => Apr 1 Jan 1 .plus(4 months) => May 1 Note how in the second half there are still variably sized months, but the result is what a human would want.
- tmp538394722 5y agoYou’ve ignored your critique and answered only the more obvious anecdotes. So I’ll repeat: What’s Feb 28th + 1 month? Does the human expect the last day of March? Or the 28th day of March? The API doesn’t make that clear - I think you could reasonably argue for either.
- brianpan 5y agoThe article is about naming the method. The fact that dates are messy means a clean API is difficult/impossible. I think plus() is a name that is good enough. I can't think of a better name that will help the user understand what will happen in the 2/28 + 1 month case. That's asking too much of a method name. That's what docs are for.
- opheliate 5y agoPerhaps .dateAfter(1 month) would be more appropriate? I sympathise with the author in finding the violation of associativity of “plus” a bit jarring.
- jimminy 5y agoIf you're adding 1 month, you're working on the month's location, e.g. 2021 - (02) - 28. When you increment the month, the result would be 2021-03-28. The only time you'd modify the day, is if the day became invalid due to an overflow, during that increment. If, when, you overflow the days you'd set the value of days to the maximum in that month. If I tell someone, I'll get to that in a month, they expect by this day in the next month, the next calendar page, not 30/31 days.
- prpl 5y agoIt should be the 28th and there should be a ceiling method.
- mjevans 5y agoI did cover that: """ None of the examples in the article use a more vague syntax, such as "0 days before the end of the month". """ As another reply points out, it's incrementing the Month set of buckets. I'll also extend with other results I expect: 2020-12-31 .plus(1 months) => 2021-01-31 2020-12-31 .plus(2 months) => 2021-02-28 2020-12-31 .plus(3 months) => 2021-03-31 2020-12-31 .plus(4 months) => 2021-04-30 A normal human has several options, and truncating to stay within the month makes the most sense to the most people most of the time. It's perfectly reasonable to take that step when resolving the indicated date to a representable value. I'll go further: JodaTime probably isn't focused on Precision Date Calculations; it behaves very much the way I expect someone working with forms and fields, general CRUD enterprisy software stuff, would want auto-filled dates to work.
- CptMauli 5y agoI still think plus is fine in this case. And really, my expectation is, that anybody who thinks about adding a months, knows about their own intention. Because I suspect just adding the month is not what most people are actually doing, I guess in 90% of the cases most developers will add another step, rounding to the last (or first) day, or to the next same weekday, whatever. The one thing nobody wants, is to add a month and land in the month one over. So even if the semantic differs between libraries for adding a month, it actually doesn't mather that much. Because for all the other cases one could imagine, most people will add days or weeks, if staying on the same day matters.
- da_chicken 5y agoThe problem is that "What is one month after 2021-02-28?" doesn't have a single meaning in plain human language. It could mean 30 days in the future. Or the same day of the week 4 weeks in the future (i.e., 28 days in the future). Or the day of the same cardinality in next month. Or any date in the next month. And those are all equally correct. It's simply not a precise measure of time when spoken from one human to another human in plain language. Indeed, I think we inherently understand it to be an imprecise measure of time just as much as "tomorrow" doesn't mean "exactly 86,400 seconds from this moment". "Next week" doesn't necessarily mean "7 days from now", either. That's why computers don't typically use imprecise terms. They provide feedback and say "this will occur at this time and date". You always have to check what the operators actually do and what the requirements actually mean when you're working with times and dates.
- jimminy 5y agoPlus/Increment/Add 1 month, does have one single meaning, it's that the outcome may be invalid that is the issue. "2021-01-31".plus(unit="Month", size=1) => "2021-02-31" But nobody really wants that, because it's not a valid date. So implicitly the library is deciding to return a valid date. A library could be written to just provide invalid dates, and let the end user handle any errors. That library could also include an explicit validation method that takes a date and returns a valid one. "2021-02-31".coerceToValid() => "2021-02-28" // Overflow == Max "2021-02-31".coerceToValid(asDays=True) => "2021-03-03" // Overflow Carries (to the right) In fact the library, that provides an ignorant response and no contract on validity would hold to the associative property, it just wouldn't be as ergonomic.
- saurik 5y agoFWIW, I believe there are only two ways to build this library correctly: the way you described here (where intermediate date values are allowed to be invalid) and simply making "one month after 2021-01-31" throw an exception.
- jimminy 5y agoI agree those are the technically correct way to do it. But if the reason people are wanting to use the library, is they want something to handle the complexity for them, coercing the data is good for simplication. There is actually another option, that provides idempotent/associative consistency and implicit coercion to valid values. This option, which discards some use cases (days beyond 28, when manipulating months), you coerce all values 29..31 to 28. This isn't even as technically correct, as the original option, but it removes the inconsistency and holds to the simplification contract to users.