d = Date(2010,01,01)
How can I replace the year with 2011?
d = Date(2010,01,01)
How can I replace the year with 2011?
julia> using Dates
julia> d= Date(2010,01,01)
2010-01-01
julia> d = d + Year(1)
2011-01-01
julia> using Dates
julia> adate = Date(2010,01,01)
2010-01-01
julia> ayear, amonth, aday = yearmonthday(adate)
(2010, 1, 1)
julia> a2011date = Date(2011, amonth, aday)
2011-01-01
According to the docs, dates’ objects are immutable Int64
wrappers, so it seems that they cannot be replaced (in place) but have to be re-constructed.
That is correct. The best we can do is to reassign a revised date to the same var.
I don’t want to change it in place. I want something like
d = Date(2010,01,01)
new_date = replace(d, year=1999)
Is there a function or package that supports this kind of thing?
BangBang.jl and SetField.jl do it for some structs, but Date
doesn’t have a year
field so those don’t work.
>> replaceparts(d::Date; year=year(d), month=month(d), day=day(d)) = typeof(d)(year, month, day)
>> replaceparts(Date(2001,01,01), month=4)
2001-04-01
This works, though maybe there’s a better way via ConstructionBase/BangBang. I’m not sure how to use those packages without piracy. (cc @tkf @jw3126 if you happen to have any thoughts)
How about this?
julia> t = Date(2001,01,01);
julia> t_new = t + Year(1)
2002-01-01
That was suggested above but I don’t want the output year to depend on the input year.
It shouldn’t be hard to write a function for that though
new_year(d::Date, y)
diff = year(d) - y
return d + Year(diff)
end
What about:
julia> replaceyear(d, y) = Date(y, monthday(d)...)
julia> replaceyear(today(), 2000)
2000-09-17
Your solution looks really nice.
@jzr Since year
is not a property of Date
:
julia> using Dates
julia> d = Date(2021, 9, 18)
2021-09-18
julia> d.year
ERROR: type Date has no field year
I would rather not use ConstructionBase
. Instead, the appropriate way to get the year is the year
function. So in my view the appropriate way to set year
would be to overload Accessors.set:
julia> using Accessors, Dates
julia> Accessors.set(d::Date, ::typeof(year), y) = Date(y, month(d), day(d))
julia> d = Date(2021, 9, 18)
2021-09-18
julia> @set year(d) = 2022
2022-09-18
This makes things insanely composable and for instance gives nested updates for free:
julia> struct Person; name::String; birth::Date; end
julia> p = Person("Julia", Date(2009, 8, 22))
Person("Julia", Date("2009-08-22"))
julia> @set year(p.birth) = 123
Person("Julia", Date("0123-08-22"))
If you like this solution, a PR to Accessors would be welcome.
@jw3126, this looks amazing (linking also your nice JuliaCon2020 talk on Setfields.jl).
In order to set year, month and day, do we need to write three times the Accessors.set constructor, as per your example, or is there a one-line option?
Thank you.
In order to set year, month and day, do we need to write three times the Accessors.set constructor, as per your example, or is there a one-line option?
In this very special case, Date(year, month, day)
is the way to set all three, but in general, the answer is “not automatically”. Since there is no “getter” function that returns all three, it is awkward to provide a setter function for all three. You could of course provide your own getter function and match it with a setter like so:
ymd(d::Date) = year(d), month(d), day(d)
Accessors.set(::Date, ::typeof(ymd), (y,m,d)) = Date(y,m,d)
This again gives you composability and allows things like @set ymd(person.birth) = (2021, 10, 2)
.
There is
yearmonthday(dt::TimeType) -> (Int64, Int64, Int64)
Simultaneously return the year, month and day parts of a Date or DateTime.
Completely overwhelmed by this computer science, but totally amazed at how it works:
using Accessors, Dates
Accessors.set(::Date, ::typeof(yearmonthday), (y,m,d)) = Date(y,m,d)
struct Person; name::String; birth::Date; end
p = Person("Julia", Date(2009, 8, 22))
julia> @set yearmonthday(p.birth) = (2021, 10, 2)
Person("Julia", Date("2021-10-02"))
Thank you.
@rafael.guerra would be awesome if you could PR this to Accessors.jl.
Would be glad to but @jzr has all the merit on this and is much more knowledgeable than me.
What would be the Accessors version of this, which supports setting any 1, 2, or 3 attributes at once?
>> replaceparts(d::Date; year=year(d), month=month(d), day=day(d)) = typeof(d)(year, month, day)
>> replaceparts(Date(2001,01,01), month=4)
julia> Accessors.set(d::Date, ::typeof(yearmonthday), year=year(d), month=month(d), day=day(d)) = Date(year, month, day)
julia> d = Date(2000,01,01)
2000-01-01
julia> @set year(d) = 1999 month(d) = 4
ERROR: LoadError: MethodError: no method matching var"@set"(::LineNumberNode, ::Module, ::Expr, ::Expr)
The idea is that Accessors
provides a setter for each existing Dates
getter. So all of the following should be overloaded:
set(d::Date, ::typeof(day), val) = ...
set(d::Date, ::typeof(month), val) = ...
set(d::Date, ::typeof(year), val) = ...
set(d::Date, ::typeof(monthday), val) = ...
...
I think it would make sense for Accessors
to provide these.
If that is not flexible enough, you could define in your own project on top of that
parts(d::Date) = (year=year(d), month=month(d), day=day(d))
function Accessors.set(d::Date, ::typeof(parts), patch)
Date(
get(patch, :year, year(d)),
get(patch, :month, month(d)),
get(patch, :day, day(d)),
)
end
Which could be used like @set parts(person.birth) = (day=1, month=2)
. Or just stick with replaceparts
and do
@modify(person.birth) do date
replaceparts(date, day=1, month=2)
end
for nested updates.