Add year in timeadd function or availability of Dateadd function in TF?

locals {
 secertExpryInYear=  "2y"
}

resource "azuread_application_password" "example" {
 application_object_id = data.azuread_application.application.id
 description           = "My managed password"
 value                 = "random_string.password.result"
 end_date              =  formatdate("YYYY-MM-DD", timeadd(timestamp(), local.secertExpryInYear)))  
}

Is there any specific function to add year to current timestamp ?

Hi @mukeshinit,

Unfortunately timeadd is a time-oriented rather than a calendar-oriented function, and so it only supports clock time units and not anything that would involve reference to a calendar, such as years or months.

I think the closest you could get with this function would be to reframe the problem in terms of hours, using the number of hours in a typical year. Of course, that’ll not get exactly the same result in the presence of complications such as leap years and leap seconds, but it’s exactly that sort of calendar complexity that is what makes timeadd not support “calendar units” in the first place.

Another way to approach it would be to define the rule “syntactically” instead. For example, you could say that the rule is that the end date is the instant at exactly the same wallclock time on the same month and date in the next year, regardless of the fact that this’ll end up with a different elapsed time depending on the calendar layout of the current year. You could implement that sort of syntactic approach by modifying the timestamp strings directly:

locals {
  timestamp_parts = regex("^(?P<year>\\d+)(?P<remainder>-.*)$", local.current_time)
  year_from_now   = format("%d%s", local.timestamp_parts.year + 1, local_timestamp_parts.remainder)
}

The local.timestamp_parts value here is splitting the initial timestamp so we can extract the year part in order to do arithmetic on it:

{
  "remainder" = "-04-09T17:54:39Z"
  "year"      = "2021"
}

"2021" + 1 is 2022 in Terraform, because the + operator implicitly converts all of its arguments to numbers before performing the addition, and so for me this produced the following result for local.year_from_now:

"2022-04-09T17:54:39Z"

Another thing I want to note, by the way, is that the timestamp function is an non-converging function and so it will return a new value each time you re-plan this configuration, which will then cause Terraform to constantly plan to update this object. I expect that isn’t what you wanted here, so that’s why I replaced timestamp() with the local.current_time placeholder in the example above, under the assumption that you’d populate that some other way that would remain stable on future runs.


Dealing with calendars is a non-trivial problem where correct solutions require databases that need ongoing maintenance to record special details such as leap seconds, etc, and so that’s why Terraform Core doesn’t try to get into that business.

However, as an alternative to the above workarounds within Terraform Core you might like to consider the hashicorp/time provider as alternative. It has a resource type time_offset which is designed to help with use-cases like yours:

resource "time_offset" "password_end" {
  offset_years = 1
}

resource "azuread_application_password" "example" {
  # ...
  end_date = formatdate(
    "YYYY-MM-DD",
    time_offset.password_end.rfc3339,
  )
}

A key advantage of handling this with a managed resource rather than just a function is that this uses the Terraform state as a sort of memory for what date it generated when it was “created”, so subsequent terraform plan will use the same date. (See the docs for the resource type for some options for intentionally recreating the time_offset when you need to.)

1 Like

Thank you so much for your time and for replying on this issue. Awesome explanation and it does make complete sense on terraform core timestamp limitation.

I would rather pick [resource type time_offset ] for this manipulation, so that terraform core would have record of date for the respective resource.

Thanks for the suggestion again . Appreciate your effort and timely help.

Thanks for following up, @mukeshinit! I’m glad it was helpful, and agreed that time_offset seems like a good answer for your situation.

Reading back my previous comment I realize I had meant to note something about the syntactic date manipulation that I then forgot about while I was writing: that answer is tricky because it’s assuming that all dates and times that exist in one year will exist in the next year, and there are some exceptions to that rule such as February 29 during a leap year.

Whether that’s important will depend on how strongly the recipient of that timestamp will validate it. Terraform’s own formatdate function is aware of the usual mechanical definition of a leap year (year%4 == 0 && (year%100 != 0 || year%400 == 0) at the time of writing) and so would reject a February 29 in a non-leap year:

â•·
│ Error: Invalid function argument
│ 
│ Invalid value for "time" parameter: not a valid RFC3339 timestamp: day out of range.
╵

However, for timestamps accepted by providers and remote APIs the rules might be different. Some systems will automatically “fix” a missing February 29 as the corresponding March 1 instead, or other similar behaviors.

The time_offset resource type’s offset_years does, of course, have its own definition of what “adding a year” means, and as far as I can tell it’s current implementation is something like the syntactic approach I described, but with an extra rule to roll over an out-of-range date into the following date. (Though I didn’t look closely, so I might be missing some interesting details. If it’s important to you then I’d advise looking at the implementation of time_offset.

1 Like

Thanks again for pointing out this workaround !!