January 1st is not always on the first week of the year

And related calendar pitfalls.

Puedes leer la versión en Español de este post aquí.

Some time last year I was doing some code at work related to data collection. I was adding a timestamp to the rows in the database, and for reasons that are not relevant, also added in separate columns the year, week and month of the timestamp.

Sounds simple enough. The code was pretty much this in Python:

from datetime import datetime

timestamp = datetime.now()  # 2026-03-14 14:17:08.052495

year = timestamp.year  # 2026
month = timestamp.month  # 3
week = timestamp.isocalendar().week  # 11

When I first wrote the code I didn’t stop to think about corner cases. I bet that if you ask your LLM of choice to give you a code snippet that gets the year, week and month from a timestamp, it will produce something very similar to that.

It seemed to work fine, and indeed it ran without issues for many months. However, in January this year I noticed that the data around the turn of the year was messed up. For some reason, the last few days of December had the values: year 2025, month 12, week 1. Huh??

This made it seem like the data records were added in the first week of 2025. Yes, the month value was 12, but the first week of the year is not in December!

Try it for yourself:

timestamp = datetime.strptime("2025-12-31 09:30:00", "%Y-%m-%d %H:%M:%S")

year = timestamp.year  # 2025
month = timestamp.month  # 12
week = timestamp.isocalendar().week  # 1

So I started looking for the problem, and after a bit of searching I found there is an entire Wikipedia page dedicated to this. That’s when I realized that something was very wrong with my assumptions.

The whole problem stems from the question of how do you define what is the first week of the year. And there is a standard for that: ISO 8601. If I had carefully read the documentation of the isocalendar() function that gets the week from the date, I would have realized all this much sooner.

So, what is the first week of the year? #

I had kind of a vague assumption that:

  • January 1st is always on the first week of the year
  • December 31st is always on the last week of the year

But turns out that this is totally wrong. The ISO 8601 standard says a week is a 7-day period, starting on a Monday and ending on a Sunday. Fair enough. But then the problem is clear: Unless December 31st is on a Sunday (and January 1st is on a Monday), both days are on the same week.

When you say it like that it sounds pretty obvious. But which week is that then? Is it the first week of the new year, the last week of the ending year…? Again, the ISO standard has the answer. I quote from Wikipedia:

If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in W01. If it is on a Friday, it is part of W53 of the previous year. If it is on a Saturday, it is part of the last week of the previous year which is numbered W52 in a common year and W53 in a leap year. If it is on a Sunday, it is part of W52 of the previous year.

If 31 December is on a Monday, Tuesday, or Wednesday it is in W01 of the next year. If it is on a Thursday, it is in W53 of the year just ending. If on a Friday it is in W52 of the year just ending in common years and W53 in leap years. If on a Saturday or Sunday, it is in W52 of the year just ending.

Pretty convoluted. But essentially what it means is:

  • The first week of the year is the week with the first Thursday of the year
  • In some cases, the last few days of December correspond to the first week of the new year
  • In some cases, the first few days of January correspond to the last week of the previous year

So this is why my code was putting those records to week 1! But it should have been then first week of 2026, not of 2025. It turns out that I should have used the ISO year instead of the “calendar” year. With that:

timestamp = datetime.strptime("2025-12-31 09:30:00", "%Y-%m-%d %H:%M:%S")

year = timestamp.isocalendar().year  # 2026
week = timestamp.isocalendar().week  # 1

It’s kind of counter-intuitive that some dates in 2025 correspond to year 2026. But that’s the price we have to pay if we want to use week numbers.

However we still haven’t solved the month issue. Turns out there is no such thing as an “ISO month”, so I would have to use timestamp.month. This means that even if I used ISO year in my original code, I would have still ended up with the wrong data: year 2026, week 1, month 12.

Alright, so what can you do? #

I guess there are 3 options.

1. Stick to the ISO calendar, forget about the month #

If you care more about the week than the month, just stick to the ISO calendar. It will produce consistent results, but you need to keep in mind that some dates will be assigned to the “other” year.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.isocalendar().year
  week = timestamp.isocalendar().week
  print(f"Year: {year}, Week: {week}")


# Year: 2026, Week: 1
# Year: 2026, Week: 1
# Year: 2026, Week: 1
# Year: 2026, Week: 1

For the opposite case you can try this with some dates around January 1st 2021. You’ll see that they get assigned to week 53 of 2020.

2. Stick to the calendar year and month, forget about the week #

This option is free of ISO shenanigans. 2025 dates get assigned to year 2025, period.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.year
  month = timestamp.month
  print(f"Year: {year}, Month: {month}")

# Year: 2025, Month: 12
# Year: 2025, Month: 12
# Year: 2026, Month: 1
# Year: 2026, Month: 1

3. Adjust the month #

If you really really want to keep all three, then you’ll have to adjust the month to be consistent with the ISO year and week. If you use this option remember to stick to the ISO year.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.isocalendar().year
  week = timestamp.isocalendar().week
  month = timestamp.month

  if week == 1 and month == 12:
    # This December date corresponds to the first week of January
    month = 1
  elif week > 50 and month == 1:
    # This January date corresponds to the last week of December
    month = 12

  print(f"Year: {year}, Month: {month}, Week: {week}")

# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1

I believe this works, but still I’d say use it with caution. If this post has taught us anything, it’s that messing around with dates is dangerous territory.

Bonus #

Check this list for other falsehoods programmers believe about dates and time. Perhaps “January 1st is always on the first week of the year” should be in it.