Replace date year

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
2 Likes

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.

1 Like

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)

2 Likes

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
1 Like

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.

4 Likes

@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).

1 Like

There is

  yearmonthday(dt::TimeType) -> (Int64, Int64, Int64)

  Simultaneously return the year, month and day parts of a Date or DateTime.
2 Likes

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.

1 Like

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)
1 Like

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.

2 Likes