· Fixed Assets & Depreciation · 10 min read

Date to Age Calculator: Turn Placed-In-Service Dates Into Years and Months Instantly

Select a date column and an as-of date — the macro inserts a new column showing age in years and months for every date. Non-destructive, works on any date column.

Share:

TL;DR: You have 200 fixed assets with placed-in-service dates. The partner wants a depreciation schedule showing the age of each asset as of 12/31/2026. This macro asks for the as-of date, lets you click the date column, and inserts a new column to the right showing every age in “Xy Ym” format. Original data stays untouched.

The Problem

The partner walks by your desk at 3:30 PM: “For the Henderson fixed asset schedule, can you add a column showing how old each asset is as of year-end? I want to see at a glance which assets are fully depreciated.” You open the schedule — 187 rows, placed-in-service dates in column C. The manual approach: insert a column, write a formula subtracting the date from 12/31/2026, format it as years and months, drag it down 187 rows, and hope you didn’t miss the two assets added mid-year.

This macro does it in two clicks. Tell it the as-of date, point at your date column, and it inserts a new column with every age calculated. Five seconds, no formulas to audit, no drag-handle anxiety.

#Prerequisites & Setup

What you’ll need:

  • Excel 2016+ (desktop)
  • A worksheet with a column of dates (fixed asset placed-in-service dates, employee hire dates, contract start dates — anything that’s a real Excel date)
  • Dates stored as actual Excel date values (serial numbers), not text that looks like a date

Limitations:

  • Works on the active sheet only — select the right tab before running
  • Dates must be in a single column (no scattered date cells)
  • Blank cells and non-date values are skipped and reported
  • The inserted column pushes existing columns to the right — formulas referencing those columns will shift automatically
  • Age is calculated in calendar months, not 30-day months — DateDiff("m") uses the actual calendar

#The Macro

Option Explicit

Sub DateToAgeCalculator()
    ' ── Date to Age Calculator ─────────────────────────
    ' Asks for an as-of date, then lets the user select
    ' a column of dates. Inserts a new column to the
    ' right showing the age of each date in "Xy Ym"
    ' format (e.g., "3y 4m"). Skips blanks and non-date
    ' values, reporting both in the final summary.
    ' ────────────────────────────────────────────────────

    ' ── Configuration ──────────────────────────────────
    Const HEADER_ROW As Long = 1

    ' ── State management ───────────────────────────────
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' ── Variables ──────────────────────────────────────
    Dim ws As Worksheet
    Dim asOfDate As Date
    Dim dateInput As String
    Dim dateCol As Range
    Dim colNum As Long, lastRow As Long
    Dim r As Long, months As Long
    Dim agedCount As Long, skippedCount As Long

    ' ── Error handling ─────────────────────────────────
    On Error GoTo CleanUp

    Set ws = ActiveSheet

    ' ── Step 1: Get as-of date ─────────────────────────
    dateInput = InputBox( _
        "Enter the as-of date (e.g., 12/31/2026):", _
        "As-Of Date", Format(Date, "mm/dd/yyyy"))
    If dateInput = "" Then GoTo CleanUp

    If Not IsDate(dateInput) Then
        MsgBox """" & dateInput & """ is not a valid date. " & _
               "Use mm/dd/yyyy format.", vbExclamation, "Invalid Date"
        GoTo CleanUp
    End If
    asOfDate = CDate(dateInput)

    ' ── Step 2: Select the date column ─────────────────
    On Error Resume Next
    Set dateCol = Application.InputBox( _
        "Click any cell in the column that contains dates:", _
        "Select Date Column", Type:=8)
    On Error GoTo CleanUp

    If dateCol Is Nothing Then GoTo CleanUp

    colNum = dateCol.Column
    lastRow = ws.Cells(ws.Rows.Count, colNum).End(xlUp).Row

    If lastRow <= HEADER_ROW Then
        MsgBox "No data found in column " & ColLetter(colNum) & _
               " below the header row.", vbExclamation
        GoTo CleanUp
    End If

    ' ── Step 3: Insert new column to the right ─────────
    ws.Columns(colNum + 1).Insert Shift:=xlToRight
    ws.Cells(HEADER_ROW, colNum + 1).Value = _
        "Age (as of " & Format(asOfDate, "mm/dd/yyyy") & ")"
    ws.Cells(HEADER_ROW, colNum + 1).Font.Bold = True

    ' ── Step 4: Calculate ages ─────────────────────────
    agedCount = 0
    skippedCount = 0

    For r = HEADER_ROW + 1 To lastRow
        If IsEmpty(ws.Cells(r, colNum)) Then
            skippedCount = skippedCount + 1
        ElseIf IsDate(ws.Cells(r, colNum).Value) Then
            months = DateDiff("m", ws.Cells(r, colNum).Value, asOfDate)
            ws.Cells(r, colNum + 1).Value = _
                (months \ 12) & "y " & (months Mod 12) & "m"
            agedCount = agedCount + 1
        Else
            ws.Cells(r, colNum + 1).Value = "(not a date)"
            skippedCount = skippedCount + 1
        End If
    Next r

    ' ── Step 5: Format the output column ───────────────
    ws.Columns(colNum + 1).AutoFit

    ' ── Step 6: Summary ────────────────────────────────
    Dim msg As String
    msg = "Aged " & agedCount & " date(s)." & vbCrLf & _
          skippedCount & " skipped (blank or non-date)." & vbCrLf & _
          "Output in column " & ColLetter(colNum + 1) & "."

    MsgBox msg, vbInformation, "Done"

CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic

    If Err.Number <> 0 Then
        MsgBox "Error " & Err.Number & ": " & Err.Description, _
               vbCritical, "Macro Error"
    End If
End Sub

' ── Helper: Convert column number to letter ────────────
Private Function ColLetter(colNum As Long) As String
    ColLetter = Split(Cells(1, colNum).Address(True, False), "$")(0)
End Function

#How It Works

#Two InputBoxes, no code editing

The macro uses two prompts before it does anything. The first is a standard InputBox — type the as-of date in mm/dd/yyyy format. It defaults to today’s date so you can just press Enter if you’re aging as of now. The IsDate check catches typos like “12/32/2026” before the macro starts writing.

The second prompt uses Application.InputBox(Type:=8) — the mouse-select variant. Click any cell in your date column and Excel captures the range. The macro extracts just the column number, so it doesn’t matter which row you click.

Set dateCol = Application.InputBox( _
    "Click any cell in the column that contains dates:", _
    "Select Date Column", Type:=8)
colNum = dateCol.Column

If the user cancels either prompt, GoTo CleanUp exits without touching the worksheet.

#DateDiff does the heavy lifting

months = DateDiff("m", ws.Cells(r, colNum).Value, asOfDate)
ws.Cells(r, colNum + 1).Value = _
    (months \ 12) & "y " & (months Mod 12) & "m"

DateDiff("m", ...) returns the number of calendar-month boundaries between two dates. An asset placed in service on March 15, 2023, aged as of December 31, 2026, crosses 45 month boundaries — that’s 3 years and 9 months.

The integer division (months \ 12) and modulo (months Mod 12) split the total months cleanly. No floating-point math, no rounding issues.

An important subtlety: DateDiff("m") counts month boundaries, not 30-day intervals. An asset placed in service on January 31 aged as of February 1 returns “0y 1m” even though only one day passed. For tax depreciation schedules this is almost always the desired behavior — MACRS half-year and mid-quarter conventions care about which month, not the exact day count.

#Non-destructive by design

The macro inserts a new column to the right of the date column and writes results there. It never modifies the dates themselves or any other data. If you run it on the wrong column, delete the inserted column and run again. Excel’s column-insert behavior automatically updates any formulas that reference shifted columns, so your cross-sheet references stay intact.

#Three things it skips

Blank cells. Not every row has a placed-in-service date — maybe the asset was disposed, or the row is a section divider. Blank cells increment the skip count and get no output.

Non-date values. A cell containing “Various” or “N/A” instead of a date gets the label (not a date) in the output column so you can see at a glance which cells need attention.

The header row. Row 1 is assumed to be headers and is never processed — it gets the output column header instead. Change HEADER_ROW if your sheet has headers in a different row.

#The output header tells you what you did

ws.Cells(HEADER_ROW, colNum + 1).Value = _
    "Age (as of " & Format(asOfDate, "mm/dd/yyyy") & ")"

The column header includes the as-of date so six months from now, when you reopen the workbook, you know exactly which date was used. No mystery “Age” column with unknown provenance.

#Why the message box lists the output column

msg = "Aged " & agedCount & " date(s)." & vbCrLf & _
      skippedCount & " skipped (blank or non-date)." & vbCrLf & _
      "Output in column " & ColLetter(colNum + 1) & "."

If you expected 187 results and got 185, the skip count tells you two cells need attention. The column letter in the message saves you from scrolling right to find the output.

#Adapt It

Get the next macro in your inbox

One copy-paste-ready macro recipe every two weeks. No spam, no VBA theory — just automation that saves you time.

One macro recipe every two weeks. Unsubscribe anytime.

E

Excel Macro Guy

Excel enthusiast · married to an accountant

I love Excel. My wife is an accountant. Every busy season, I watch her wrestle with workpapers and think "a macro could do that in half a second." So I build them. She tests them on real client data. What survives gets published here.

More about me →