DateFormatter's New Year's Bug - YYYY vs yyyy

Why your iOS date formatting breaks around New Year's and how to fix it

Every year around New Year's Eve, a wave of date-related bugs show up in iOS apps. The reason is a single character mistake in DateFormatter: using YYYY instead of yyyy.

The Bug

DateFormatter has two different year specifiers:

  • yyyy - Calendar year (what you almost always want)
  • YYYY - ISO week-based year (rarely what you want)

The ISO week-based year can differ from the calendar year during the first and last few days of the year. When a week spans two calendar years, the ISO week-based year uses the year that contains the majority of that week.

When This Bites You

On December 30th, 2024 (a Monday), the ISO week-based year is already 2025 because that Monday starts ISO week 1 of 2025. But the calendar year is still 2024. This year it showed up Monday 29th December 2025, the first day of the week it becomes 2026.

import Foundation

let formatter = DateFormatter()
let dec30_2024 = Calendar.current.date(from: DateComponents(year: 2024, month: 12, day: 30))!

// Wrong - uses ISO week-based year
formatter.dateFormat = "YYYY-MM-dd"
print(formatter.string(from: dec30_2024))
// Output: 2025-12-30

// Correct - uses calendar year
formatter.dateFormat = "yyyy-MM-dd"
print(formatter.string(from: dec30_2024))
// Output: 2024-12-30

Here's a table showing how dates around the 2019/2020 boundary behave differently:

Date Weekday yyyy (Calendar) YYYY (ISO Week) ISO Week
2019-12-29 Sunday 2019-12-29 2019-12-29 52
2019-12-30 Monday 2019-12-30 2020-12-30 01
2019-12-31 Tuesday 2019-12-31 2020-12-31 01
2020-01-01 Wednesday 2020-01-01 2020-01-01 01

Notice how December 30th and 31st, 2019 show as "2020" when using YYYY because they fall in ISO week 1 of 2020.

Real-World Bug Example

Imagine you're generating log filenames in your app:

let formatter = DateFormatter()
let dec31_2019 = Calendar.current.date(from: DateComponents(year: 2019, month: 12, day: 31))!

// Bug: This creates a file with the wrong year!
formatter.dateFormat = "''YYYY-MM-dd'.txt'"
print(formatter.string(from: dec31_2019))
// Output: 2020-12-31.txt

// Correct: Uses the actual calendar year
formatter.dateFormat = "''yyyy-MM-dd'.txt'"
print(formatter.string(from: dec31_2019))
// Output: 2019-12-31.txt

Your 31st December 2019 log file ends up named 2020-12-31.txt.

When to Use Each

Use yyyy for:

  • User-facing dates
  • Filenames and paths
  • Database records
  • API date strings
  • Anything where you want the actual calendar year

Use YYYY only when:

  • Displaying ISO 8601 week dates (e.g., "2020-W01-3")
  • Specifically working with ISO week numbering
  • Always paired with the week number format specifier (ww)

The Rule

If you're not explicitly working with ISO week numbers, you want lowercase yyyy. The uppercase YYYY variant exists for a specific use case (week-based date systems), and using it for general date formatting is almost always a bug.

This is one of those bugs that's invisible 360+ days of the year, then strikes during New Year's when everyone's on vacation.