7
$\begingroup$

DateValue[..., "YearExact"] can be used to get a date as an non-integer year. This can be useful for some quick, not necessarily too precise calculations where a partial calendar year is a practical quantity. For instance:

DateValue[Today, "YearExact"] (* 2022.5506849315068 *) 

How to get a reverse of this, a date object from a number like above? Is there a preferred way?

My current attempt seems like a bit of a kludge, including a rudimentary round-up second:

ClearAll[exactYearToDate] exactYearToDate[y_] := DateObject[ DateObject[{2000}, "Instant"] + Quantity[y - 2000, "Years"] + Quantity[1, "Seconds"], "Day"] 

It works, but I don't believe this is the way things should be done - but is there anything similar built in?

{Today, exactYearToDate[DateValue[Today, "YearExact"]]} (* {DateObject[{2022, 7, 21}, "Day"], DateObject[{2022, 7, 21}, "Day", "Gregorian", 3.]} *) 
$\endgroup$

2 Answers 2

5
$\begingroup$

I don't know any built-in that could do this, but here are two alternatives:

JulianDate

JulianDate uses "DayExact", from a defined origin, If we convert the fraction to "DayExact", we could use FromJulianDate:

ClearAll[YearExactToDateObject1]; YearExactToDateObject1[number_, opt___] := Block[{year = IntegerPart[number]}, FromJulianDate[ JulianDate[{year}] + FractionalPart[number]*(365 + Boole[LeapYearQ[{year}]]), opt] ] 

This method is around ~7 times faster and uses 5x lower memory!

You can use the same technique with FromAbsoluteTime and AbsoluteTime or UnixTime and FromUnixTime but I prefer to stick to the highest granularity that I can and leave the rest to Mathematica.

DateObject/DateList

Another solution is with DateObject/DateList since the day position ({year, month, day, h, m, s}) accepts real values but has a caveat:

ClearAll[YearExactToDateObject2]; YearExactToDateObject2[number_, opt___] := Block[{year = IntegerPart[number]}, DateObject[{year, 1, 1 + (365 + Boole[LeapYearQ[{year}]])*FractionalPart[number], 0, 0, 0.00001}, opt] ] 

This method is 50% faster than the previous (14 times faster in general) and uses ~5 times lower memory! But the caveat is 0.00001 in the second (maybe rounding-off error). A huge benefit is that because we used DateObject, we can use other calendars (CalendarType needs to be provided to LeapYearQ in that case):

YearExactToDateObject2[1401.46516, TimeZone -> 0, CalendarType -> "ArithmeticPersian"] (* Out: DateObject[{1401, 6, 15, 18, 48, 5.76001}, "Instant", "ArithmeticPersian", 0.] *) 

opt is used to pass options to FromJulianDate or DateObject respectively.

If you used them without TimeZone options, you won't have any problem (I hope) but if you specify, you'll get different results:

DateList@ YearExactToDateObject1[DateValue[Now, "YearExact"], TimeZone -> 3.5] (* Out: {2022, 7, 21, 19, 0, 25.403} *) DateList@ YearExactToDateObject2[DateValue[Now, "YearExact"], TimeZone -> 3.5] (* Out: {2022, 7, 21, 20, 0, 25.402} *) 

This situation will get more complex if you set $TimeZone in a Block. These edge cases can be handled but I decide to keep the definitions simple.

I haven't fully tested them in a full range of dates, and 0.00001 might not be enough. So I won't recommend using it but if you work on higher granularities and second is not important, you can enjoy its performance.

$\endgroup$
0
$\begingroup$

I would try with this:

fromExactYear[years_] := FromUnixTime[ToExpression[ UnixTime[DateObject[{Floor[years]}]]] + ((UnixTime[ NextDate[DateObject[{Floor[years]}], "Year"]] - ToExpression[UnixTime[DateObject[{Floor[years]}]]])* FractionalPart[years])] 

It calculates the date that would correspond to the year with fraction, based on unix time. For any year should work since the each calculation takes into account the ammount of Unix time which that specific year should have (Since some years have a little more UnixTime).

$\endgroup$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.