Fix the timezone shift in monthly active users

Review the AI's codeMedium18 min

Your task

An AI wrote your monthly active users query, and finance says March is double counting compared with their dashboard. The query bucketed UTC timestamps by month, but finance reports in a local calendar, and only logins and purchases count as activity. You write the metric out with both rules made explicit.

An assistant wrote monthly_active_users(event_rows, qualifying_types, tz_offset_hours). It is in the editor. It has a bug. Find it and fix it.

Input and output:

  • event_rows: one event per row, written user_id|timestamp|event_type. The timestamp is UTC, written YYYY-MM-DDTHH, such as 2026-03-01T02. Years are from 1001 to 9998.
  • qualifying_types: the event types that count as activity.
  • tz_offset_hours: the reporting calendar's offset from UTC in whole hours, such as -5.
  • Return one YYYY-MM=count string per month, sorted by month.

Rules the tests check:

  • If qualifying_types is empty, or tz_offset_hours is below -12 or above 14, return [].
  • Split each row on |. Skip it unless it has exactly 3 parts, a user id that is not empty, and an event type that exactly equals one of qualifying_types.
  • Skip a row whose timestamp is not exactly 13 characters in the form YYYY-MM-DDTHH with digits from 0 to 9. Also skip it when the month is not 01 to 12, the day does not exist in that month, or the hour is above 23. February has 29 days in leap years: years divisible by 4, except years divisible by 100 that are not divisible by 400.
  • Move the event into the reporting calendar: add tz_offset_hours to the hour. If the result is below 0, the event falls on the previous day. If it is 24 or more, it falls on the next day. The event's month is the month of that local day.
  • Count each user once per month. Write each month as YYYY-MM, such as 2026-02.

Examples

monthly_active_users(event_rows: list[str], qualifying_types: list[str], tz_offset_hours: int) -> list[str]
Example 1
Inputmonthly_active_users(["u1|2026-03-01T02|login", "u1|2026-03-01T14|login", "u2|2026-03-15T09|purchase", "u2|2026-03-16T09|heartbeat"], ["login", "purchase"], -5)
Output["2026-02=1", "2026-03=2"]
Example 2
Inputmonthly_active_users(["u1|2026-04-01T12|login", "u1|2026-04-02T12|login", "u1|2026-04-03T12|purchase"], ["login", "purchase"], 0)
Output["2026-04=1"]
Example 3
Inputmonthly_active_users(["u3|2026-02-28T20|login"], ["login"], 5)
Output["2026-03=1"]