How to convert month to other duration measurement types? - date

For some duration-related calculations I need to convert values measured in "months" to other formats, such as years, days, or hours.
For example, what is the proper way to measure a month in terms of days? is it 30 days? or 30.4375 days? (365.25 / 12) and which format would be useful in which cases?
If you have any information on the casual/business use cases for such conversions it would be helpful too.

Unfortunately, there's really no single generally valid answer to your question.
If this is for business use, first check whether there are any existing relevant standards or business practices that define what a "month" means in your business context. If yes, you should follow that definition as closely as possible, however silly or awkward it may seem.
For casual use, the simplest solution is probably to pick any widely use date manipulation library and do whatever it does. The default behavior may not be perfect, but it's probably at least close to a fairly sensible compromise of the many contradictory expectations that users of such a library may have.
OK, but what if you insist on rolling your own solution? In that case, the first choice you should make is how you want to represent date / time values. There are at least two common choices:
The first option is to store dates / times using a simple linear count of fixed time units from a given epoch, such as Julian days or Unix timestamps. This provides a simple and compact date/time representation, makes comparing timestamps and simple date/time arithmetic (like adding n seconds to a time value) easy, and ensures that any time value corresponds to a (more or less) unique and well defined point in time.
The downside, as you've noticed, is that arithmetic using "fuzzy" time units like months or years gets difficult: you can define a year as 365.25 days (or as 365.2425 days, to take into account that only 97 out of every 400 years are leap years in the Gregorian calendar) and a month as 1/12 years, but this will cause adding a year to a date-time value to also shift the time of day by (about) 6 hours, which may be unexpected.
This approach also doesn't let you easily represent "floating" time value, like times of day without a specified date and time zone. (You can sort of deal with floating time zones by doing your time math in UTC and just pretending that it's in your local time zone, but this can cause weird stuff to happen around DST changeovers.) Conversely, it can also cause difficulties if you need to represent imprecise date/time values, such as dates without a time component.
In particular, if you choose the "natural" representation, where imprecise datetimes are represented by their starting point, so that e.g. an unspecified time of day defaults to 00:00:00.0, then anything that causes the time part to be reduced by even a fraction of a second — like, say, shifting to a later time zone, or subtracting a fuzzy time unit that is not an integral number of days — will flip the date part to the previous day. For example, with this representation, subtracting one year (= 265.2425 days) from January 1, 2014 will yield a date in 2012 (specifically, December 31, 2012, 17:56:32)!
You can avoid some of these issues by representing imprecise date/time values by their midpoints instead, so that e.g. the date 2014 is treated as shorthand for June 2, 2014, 12:00:00. What you lose, with this representation, is the ability to build datetimes just by adding up components: with this representation, 2014 + 5 months + 3 days isn't anywhere near May 3, 2014.
Also, just when you think you've at least got simple non-fuzzy time arithmetic unambiguously sorted out, someone's going to tell you about leap seconds...
The alternative approach is to store datetime values in decomposed year / month / day / hour / minute / second / etc. format. With this presentation, time intervals are also naturally stored in a decomposed format: "one month + 17 days" is, in itself, a valid time interval in such a representation, and need not (and should not) be simplified further.
This has a few obvious advantages:
Fuzzy unit arithmetic is (conceptually) simple: to add one year to a date, just increment the year component by one.
Imprecise date/time values can be naturally represented: for a pure date value, the time-of-day components can simply be left undefined (= e.g. represented by negative values for the undefined components, or simply by having each datetime value store its precision).
You have precise control over when and if rollover occurs: adding a year to a date in 2014 will always yield a date in 2015.
You can also support floating time values, such as times of day without a specified date, or dates of year without a specified year. Floating time zones also become supportable.
That said, there are some disadvantages, too:
Implementing date arithmetic gets more complex, since you have to deal with non-trivial carry/borrow rules. (Quick! What's the date 10,000,000 seconds after May 3, 2014?)
You'll still have ambiguities with month arithmetic: what's the date one month after January 31? And does it depend on whether it's a leap year or not?
You can allow such a format to store "impossible" dates like "February 31", with an optional method to normalize them to, say, February 28 (or 29, for a leap year) later. This has the advantage of preserving (some) arithmetic consistency: it allows (January 31 + 1 month) + 1 month to equal March 31 as expected.
In some ways, though this merely postpones the problem: presumably, January 31 + 24 hours should fall on February 1, but what day and month should January 31 + 1 month + 24 hours fall on? The "obvious" choice would be March 1, but whatever you choose, there will be some sequence of arithmetic operations that will yield inconsistent results.

Related

Is there a recommendation or norm on how to add months to a date when there is carry?

ISO-8601 defines time intervals, for example P1M is one month.
However it seems that is does not mandate how to determine what day is one month from a given date.
I looked up the documentation of sqlite and in their implementation, given YYYY-MM-DD, adding one month is adding 1 to MM, and then normalizing (if MM is greater than 12, then increment years, then if DD is greater than the number of days of the resulting month, then carry to next month).
However this can produce inconsistencies:
2020-01-29 + P1M = 2020-02-29
2020-01-30 + P1M = 2020-03-01
2020-01-31 + P1M = 2020-03-02
2020-02-01 + P1M = 2020-03-01 ‽, note that this this is sooner than previously
Moreover, with this method, if I specify an interval of one month and one day, should I first add one month, then one day, or should I add first one day, then one month?
2020-01-30 + P3D + P1M = 2020-02-02 + P1M = 2020-03-02
2020-01-30 + P1M + P3D = 2020-03-01 + P3D = 2020-03-04, so later if we add months first
The question is: is there anywhere a canonical way to proceed when adding an interval to a date, when the interval specifies years or months, which are variable durations?
The actual implementation of the term month in interval form is left mostly unspecified in most, if not all, standards on purpose.
The loosely accepted definition is about 30 days. The integer rounding of 365.25 / 12.
The method used varies on several factors:
Simplicity and Convenience:
It is easier to remember common days.
My electric bill is due on the 11th of every month. This is problematic when day is greater than 28.
I get paid the last day of the month.
The meeting is on the second Tuesday of each month.
Ease of calculation:
Fixed Month definition.
30 day grace period for new purchases.
Astronomical:
Lunar Based:
My apologies for the poorly condensed descriptions.
Synodic: Based on phases of the Moon: 29.18 to about 29.93 days(formed the basis of our modern system)
Sidereal: Based on "fixed" star passing. 27.321 days
Tropical: Based on celestial bodies at the Spring(northern hemisphere) Equinox: 27.321 days
Anomalistic: based on angle off of the elliptic orbit: 27.554 days
Draconic: based on angle off of the elliptic plane: 27.212 days
An even solar month would be:
About 365.2422 / 12 ~= 30.43685 days
An even calendar month would be:
Non-leap years: 30.4166666667 days
Leap years: 30.5 days
Or in terms of Weeks:
Just over 4 Weeks.
This is not meant to be an exhaustive list. There are many more historic or esoteric definitions not included here.
If I have missed one in use today, please let me know.
The main take away is that no single definition fits all purposes.
Be consistent and transparent:
Pick one and stick with it.
Let everyone know.

Formula to add days to a Gregorian date

I was looking at Tomohiko Sakamoto's weekday calculator. It's a formula to calculate the day-of-week directly given year, month, day. That made me wonder what other neat date calculation shortcuts exist.
In particular, given an input date as (in_year, in_month, in_day) and a number of days N to add, what's a formula for returning the output (out_year, out_month, out_day)? Is there a well-known trick like the algorithm above?
One way would be to convert the input to a Julian day (a count of days since 4713 BC), add N to it, and then convert back. There are formulas for conversion in both directions. But the combined formula would be quite unwieldy. Is there a simplified version?
Perhaps there is even a formula to move forward or back by a certain number of weekdays.
This question isn't "how do I do date arithmetic in my favourite programming language?" I know how to call the date library to perform these operations. It's more curiosity and the hope of starting a collection of cool date algorithms.
Some of the answers in Algorithm to add or subtract days from a date? will be relevant to this. In particular http://howardhinnant.github.io/date_algorithms.html gives code to convert y,m,d to a count of days and back again. Those two routines run back to back would be pretty fast.

SPSS Modeler - Date Function fpr lastweek

I wanted to know if it was possible to use a date function for last week's date ranges. Currently I'm using the method as below.
Thanks.
datetime_date('Sale.Date') >= datetime_date(2016,04,18)
and
datetime_date('Sale.Date') <= datetime_date(2016,04,24)
Not a great solution but:
--datetime_date(datetime_year(#TODAY), datetime_month(#TODAY), datetime_day(#TODAY)-7)
This will work for all but 7 days of the month.
You could also get a rough estimate by calculating the following (note that you will use the method I outline and not the equations. you must copy paste the equations into their designated locations).
--a=date_in_years(#TODAY)-7/365
--year=a-(a mod 1)+1900
--month=(a mod 1)-((a mod 1) mod 1/12) *12
etc. then entering these formulas into this formula:
--datetime_date(year,month,day)
OR you can use SQL to calculate a 7 day difference. This will be the accurate and easy but would require the correct setup.
It is a little unclear what you are looking for exactly; what do you mean by "last week"? Is that the 7 days prior to today or is it the last calendar week? If it's the calendar week, which definition are you using? What day of the week does a week start? When does the first week of the year start?
Unfortunately, these definition vary between different parts of the world and even for different uses within countries.
Dates in SPSS Modeler are all represented as the number of seconds from Jan. 1st, 1900, so if you are looking for the dates of the past 7 days, the calculations are fairly trivial as you can use the datetime_in_seconds() function to get the numeric representation of any date or timestamp.
If you are looking for calendar weeks, things get a little more complicated, but I should be able to help with that, too, if you can answer the above questions.
For SPSS Modeler you can use a Derive Node and I see two options to do that:
A)
Derive Node: date_weeks_difference(date1,date2)
And then use a Filter node to keep only the derive node = 1
B)
Or you can use a function inside the Derive Node, creating a dummy variable:
if date_weeks_difference(date1,date2)= 1 then 1 else 0 endif

How many days in ISO8601 duration 'months' and 'years'?

I need to represent a duration of 100 days.
From Wikipedia:
Durations are represented by the format P[n]Y[n]M[n]DT[n]H[n]M[n]S
But for a duration, how many days are in a month? Surely it depends on which month we're in... Ditto, days in a year.
Following up on my comment, it looks like it's perfectly legal to specify a count larger than the number of units that make up the next "larger" unit, so you could just use P100D. Wikipedia says:
The standard does not prohibit date and time values in a duration representation from exceeding their "carry over points" except as noted below. Thus, "PT36H" could be used as well as "P1DT12H" for representing the same duration. But keep in mind that "PT36H" is not the same as "P1DT12H" when switching from or to Daylight saving time.
While the example is for hours, it seems like days should be fine too (and as your problem illustrates, even more useful, since months and years aren't fixed quantities). That said, the standard doesn't specify the maximum number of digits for each unit, only:
Leading zeros are not required, but the maximum number of digits for each element should be agreed to by the communicating parties.
So whatever is consuming your durations must accept at least 3 digits per element for P100D to work; that doesn't seem like an unusually high level of required support.

calculating number of days between 2 days tcl

I am calculating the number of days between two dates(the 2 dates are in seconds). The following gives me the coorect result but it gives me a negative value.
For example -7.0. I am not sure why..
Also can I remove the decimal point and display?
set interval [expr { $start_date - $get_date }]
set days [expr { floor($interval / (60*60*24)) }]
puts "Start Date: $start_date <br>"
puts "Stop Date: $get_date <br>"
puts "Total number of dates betwen 2 days: $days"
You're subtracting the end (assuming that's what $get_date is) from the start. Think of it like numbers, where a later date is a bigger number - if you subtract a large number from a small number, you get a negative value, right?
So you probably just want to reverse the arithmetic:
set interval [expr { $get_date - $start_date }]
I've no idea about the decimal point part, I'm afraid... perhaps (based on this documentation) just:
set days [expr { int($interval / (60*60*24)) }]
EDIT: As noted in comments, some care is required when it comes to "days" between two events. Do you mean elapsed days, 24 hours per day, or "local" days? Are your input values in local time, UTC, or something else? Could you perhaps use a dedicated date/time library to handle this? (I have no idea whether tcl has such a thing, but I'd expect it to.)
You should think really carefully about exactly what behaviour you want in what situation, and write tests accordingly.
Not all days are 86400 seconds long, because of little things like daylight savings time. Because of this, you need to do some rounding in your calculation (and use floating point division):
set days [expr {round($interval / double(60*60*24))}]
You also usually subtract the start from the end when calculating the length of an interval, not the other way round. Gets the sign right.