Getting Started with Date and Time Problems in Programming

The first time date/time logic hurt me in production, it wasn’t a hard algorithm. It was a “simple” reminder email that went out one hour early for a chunk of users—only on one Sunday in March—only in certain regions. The bug wasn’t in SMTP or queues. It was my mental model: I treated “time” like a single thing, when it’s really two different beasts: a point on a timeline (an instant) and a human calendar expression (a local date/time).

If you’re getting started with date and time problems, you don’t need to memorize every calendar quirk. You need a reliable workflow, a few core concepts, and patterns that keep you from writing your own broken mini-calendar. In this post I’ll show you how I approach common beginner-friendly tasks—12/24-hour conversions, unit conversions, leap years, week/day conversions, counting days between dates—while building the mental model you’ll reuse in real applications.

You’ll come away with:

  • A practical way to classify date/time tasks so you choose the right data type and library
  • Runnable examples in Python and JavaScript
  • A checklist for time zones, daylight saving time, and “why is this off by one day?” bugs
  • Testing habits (including a 2026-style workflow with AI-assisted test generation) that catch edge cases early

The Two Kinds of Time: Instants vs Calendars

When you say “March 10, 2026 at 9:00 AM”, you might mean different things depending on context. I always start by asking: are we dealing with an instant on a global timeline, or a calendar value meant for humans?

1) Instant (timeline time)

An instant is a specific point in time, independent of time zone. On servers and in databases, I prefer:

  • Unix epoch seconds/milliseconds (an integer) or
  • A timezone-aware timestamp object (internally still an instant)

Use instants for:

  • Logging, metrics, tracing
  • Event ordering
  • Expiration, TTL, token lifetimes
  • Scheduling jobs that must happen at a precise moment globally

If you store an instant, you can render it for any user later.

2) Calendar time (human time)

A calendar value is how humans describe time: local dates, local times, weeks, weekdays. A “birthday” is typically a date without a time zone. A “store opens at 9:00 AM” is a local time tied to a location.

Use calendar types for:

  • Dates like 2026-02-01 (no time of day)
  • Times like 09:00 (no date)
  • Local date+time like 2026-02-01 09:00 in America/New_York

The beginner trap: “naive datetime” everywhere

Many languages let you create a date-time without a time zone (often called “naive”). That’s fine for pure calendar math in one agreed-upon zone, but it’s dangerous when you cross system boundaries.

My rule: if a value leaves your process (API response, DB write, message queue), it should be either:

  • An instant (timestamp) or
  • A calendar value with explicit context (date + time zone, or date-only)

Here’s a quick decision table I use:

Task

Best representation

Why —

— “Send reminder at 2026-02-10 09:00 user local”

local date-time + user time zone

The user cares about their wall clock “Token expires in 15 minutes”

instant (epoch + duration)

Globally consistent “Days between two dates”

date-only (calendar)

Avoid DST affecting day counts “Convert 12h to 24h”

string/time-of-day

It’s formatting, not timeline math

Parse, Normalize, Store, Present: A Workflow That Avoids Pain

Most date/time bugs come from skipping a step. I stick to a four-stage workflow.

Stage 1: Parse input into a typed value

Input is usually a string. Don’t do arithmetic on strings. Parse into:

  • a date-only type (calendar date)
  • a time-only type (time-of-day)
  • a timezone-aware instant

Stage 2: Normalize for computation

Normalization means: convert into the representation your logic needs.

Examples:

  • If you’re comparing timestamps, normalize everything to an instant in UTC.
  • If you’re counting “calendar days”, normalize to date-only.

Stage 3: Store in a stable form

For storage and interchange, I recommend:

  • For instants: ISO-8601 with offset (e.g., 2026-02-01T18:22:05Z) or epoch seconds/ms
  • For calendar dates: YYYY-MM-DD
  • For time-of-day: HH:MM:SS or HH:MM
  • For zoned local times: store local date-time + IANA time zone name (like America/Los_Angeles), not just -08:00

Why I prefer IANA zones over offsets: offsets change with daylight saving time, while the zone name captures the rules.

Stage 4: Present (format) at the last moment

Formatting is user-facing. Keep it at the edges.

Traditional vs modern approach (what I do in 2026):

Problem

Traditional approach

Modern approach I recommend —

— Parsing timestamps

Hand-written parsing or fragile regex

Strict ISO parsing via standard libs; reject invalid input Time zones

Store offsets like -0500

Store IANA zone + instant; derive offset per date Scheduling

Cron with local time assumptions

Store schedule + zone; compute next run using timezone rules Testing

A few hard-coded examples

Add edge-case generators + property checks; use AI to propose cases

Conversions You’ll Actually Code (12/24h, Units, Weeks/Days)

A lot of “getting started” problems are conversions. They look easy—and they are—until you mix in parsing mistakes.

12-hour to 24-hour conversion (Python)

Common inputs: 07:05:45PM, 12:00:00AM, 12:00:00PM. The tricky bits are the 12s:

  • 12:xx AM00:xx
  • 12:xx PM12:xx

from future import annotations

def to24h(time12h: str) -> str:

# Expected format: HH:MM:SSAM or HH:MM:SSPM (no spaces)

suffix = time_12h[-2:]

hh, mm, ss = time_12h[:-2].split(":")

hour = int(hh)

if suffix == "AM":

hour = 0 if hour == 12 else hour

elif suffix == "PM":

hour = hour if hour == 12 else hour + 12

else:

raise ValueError(f"Invalid suffix: {suffix}")

return f"{hour:02d}:{int(mm):02d}:{int(ss):02d}"

def main() -> None:

samples = [

"07:05:45PM",

"12:00:00AM",

"12:00:00PM",

"01:02:03AM",

]

for s in samples:

print(s, "->", to_24h(s))

if name == "main":

main()

If you’re doing this in a real app, prefer parsing into a time-of-day type where your language has one. But for interview-style tasks, this string-based approach is clean and safe.

24-hour to 12-hour conversion (JavaScript)

I see bugs here when people forget that 00:xx is 12:xx AM.

function to12h(time24h) {

// Expected format: HH:MM or HH:MM:SS

const parts = time24h.split(‘:‘);

if (parts.length 3) {

throw new Error(‘Invalid time format‘);

}

const hour = Number(parts[0]);

const minute = parts[1].padStart(2, ‘0‘);

const second = (parts[2] ?? ‘00‘).padStart(2, ‘0‘);

if (!Number.isInteger(hour) |

hour < 0

hour > 23) {

throw new Error(‘Hour out of range‘);

}

const suffix = hour < 12 ? 'AM' : 'PM';

const hour12 = (hour % 12) === 0 ? 12 : (hour % 12);

return ${String(hour12).padStart(2, ‘0‘)}:${minute}:${second}${suffix};

}

function main() {

const samples = [‘00:01‘, ‘12:30:15‘, ‘23:59:59‘, ‘07:05‘];

for (const s of samples) {

console.log(${s} -> ${to12h(s)});

}

}

main();

Minutes to seconds, hours to minutes/seconds

These are straight unit conversions. The “real” lesson is about choosing integer arithmetic and validating inputs.

def minutestoseconds(minutes: int) -> int:

if minutes < 0:

raise ValueError("minutes must be non-negative")

return minutes * 60

def hourstominutes_seconds(hours: int) -> tuple[int, int]:

if hours < 0:

raise ValueError("hours must be non-negative")

minutes = hours * 60

seconds = hours 60 60

return minutes, seconds

if name == "main":

print("5 minutes ->", minutestoseconds(5), "seconds")

m, s = hourstominutes_seconds(2)

print("2 hours ->", m, "minutes,", s, "seconds")

Weeks to days/hours, days to weeks

One subtlety: “week” conversions are usually fixed units (7 days), but in calendar scheduling a “week” can mean “next Monday”. For beginner conversion tasks, treat weeks as 7 days.

function weeksToDays(weeks) {

if (!Number.isFinite(weeks) || weeks < 0) throw new Error('weeks must be non-negative');

return weeks * 7;

}

function weeksToHours(weeks) {

return weeksToDays(weeks) * 24;

}

function daysToWeeks(days) {

if (!Number.isFinite(days) || days < 0) throw new Error('days must be non-negative');

return days / 7;

}

console.log(‘3 weeks ->‘, weeksToDays(3), ‘days‘);

console.log(‘3 weeks ->‘, weeksToHours(3), ‘hours‘);

console.log(‘10 days ->‘, daysToWeeks(10), ‘weeks‘);

If you care about “whole weeks plus remaining days”, return both parts:

  • wholeWeeks = Math.floor(days / 7)
  • remainingDays = days % 7

Calendar Math: Leap Years, Days in Year, Month/Year Ranges

Calendar problems are where developers start reinventing the Gregorian calendar. Don’t. Use a standard library when you can. When the task explicitly asks you to implement the rule, do it carefully and write tests.

Leap year check (rule-based)

In the Gregorian calendar:

  • Years divisible by 4 are leap years
  • Except years divisible by 100 are not
  • Except years divisible by 400 are leap years

def isleapyear(year: int) -> bool:

if year % 400 == 0:

return True

if year % 100 == 0:

return False

return year % 4 == 0

if name == "main":

for y in [1999, 2000, 1900, 2024, 2026]:

print(y, "->", isleapyear(y))

I recommend you memorize those three lines. They show up everywhere.

Number of days in a given year

This becomes trivial once you have isleapyear:

def daysinyear(year: int) -> int:

return 366 if isleapyear(year) else 365

if name == "main":

print("2024 ->", daysinyear(2024))

print("2026 ->", daysinyear(2026))

Counting months between two years

Be explicit about what “between” means. I usually define it as:

  • Inclusive start year
  • Inclusive end year
  • Count of months in that year range

That’s (endYear - startYear + 1) * 12 if startYear <= endYear.

function monthsInYearRange(startYear, endYear) {

if (!Number.isInteger(startYear) || !Number.isInteger(endYear)) {

throw new Error(‘years must be integers‘);

}

if (endYear < startYear) {

throw new Error(‘endYear must be >= startYear‘);

}

return (endYear – startYear + 1) * 12;

}

console.log(‘2024..2026 ->‘, monthsInYearRange(2024, 2026), ‘months‘);

If the problem statement instead means “exclusive of endpoints” or “months strictly between January 1 of startYear and January 1 of endYear”, the formula changes. Don’t guess—define it.

Counting days between two years

This one gets messy if you try to be clever. I prefer a straightforward loop for clarity, unless the range is huge.

def daysbetweenyears(startyear: int, endyear: int) -> int:

"""Count days from Jan 1 of startyear up to Jan 1 of endyear.

Example: start=2024, end=2026 counts all days in 2024 and 2025.

"""

if endyear < startyear:

raise ValueError("endyear must be >= startyear")

total = 0

for year in range(startyear, endyear):

total += 366 if isleapyear(year) else 365

return total

if name == "main":

print("Days from 2024-01-01 to 2026-01-01 ->", daysbetweenyears(2024, 2026))

This approach is plenty fast for typical interview constraints (even 10,000 years is a small loop). If you need more speed, you can compute leap-year counts mathematically, but I only do that when needed.

Time Zones and Daylight Saving Time: The Trap Door

If you only remember one warning: never assume a day is 24 hours when local time zones are involved.

The DST problem in one sentence

On many calendars, there are days with 23 or 25 hours due to daylight saving time transitions.

That means:

  • “Add 24 hours” is not the same as “same local time tomorrow”.
  • “Difference in days” depends on whether you mean 24-hour blocks or calendar day boundaries.

What I do in real systems

  • For time arithmetic (deadlines, TTL): I use UTC instants.
  • For human schedules (“every day at 9:00 AM local”): I store the schedule and the IANA time zone.

In Python (3.9+), zoneinfo is built in on most platforms and is my default choice.

from future import annotations

from datetime import datetime, timedelta

try:

from zoneinfo import ZoneInfo

except ImportError:

ZoneInfo = None

def demo() -> None:

if ZoneInfo is None:

print("ZoneInfo not available in this runtime")

return

tz = ZoneInfo("America/New_York")

# A local time near typical DST boundaries (exact dates vary by year and region rules).

local = datetime(2026, 3, 8, 1, 30, tzinfo=tz)

plus_24h = local + timedelta(hours=24)

plus_1d = local + timedelta(days=1)

print("Local:", local.isoformat())

print("+24h:", plus_24h.isoformat())

print("+1d :", plus_1d.isoformat())

if name == "main":

demo()

Even if these two happen to match in some cases, the point stands: local-time arithmetic has calendar rules.

Common mistakes I see (and how you avoid them)

  • Mistake: storing local timestamps without zone context.

– Fix: store an instant (UTC) or store local date-time plus IANA zone.

  • Mistake: using numeric offsets as if they were zones.

– Fix: offsets are a snapshot; zones contain rules.

  • Mistake: parsing user input like 02/03/04 without specifying a format.

– Fix: require an explicit format or parse only ISO forms.

Date Differences and Durations: Counting Days Correctly

“Find the number of days between two dates” sounds simple until you mix in times, zones, and inclusivity.

First, decide what you mean

I pick one of these and state it clearly:

1) Calendar day difference: 2026-02-01 to 2026-02-03 is 2 days.

2) Inclusive day count: 2026-02-01 through 2026-02-03 is 3 days.

3) Duration in 24-hour blocks: measured in seconds/hrs, affected by DST if in local time.

For beginner problems, you almost always want calendar day difference.

Calendar day difference (Python)

This uses date objects, not datetime.

from datetime import date

def daysbetweendates(start: date, end: date) -> int:

# Returns end – start in whole calendar days.

# Example: 2026-02-01 to 2026-02-03 -> 2

delta = end – start

return delta.days

if name == "main":

start = date(2026, 2, 1)

end = date(2026, 2, 3)

print(daysbetweendates(start, end))

If you’re handed strings, parse them with a strict format:

from datetime import datetime, date

def parseisodate(value: str) -> date:

# Accept only YYYY-MM-DD

return datetime.strptime(value, "%Y-%m-%d").date()

if name == "main":

a = parseisodate("2026-02-01")

b = parseisodate("2026-02-03")

print((b – a).days)

Inclusive day count (Python)

This is a classic “off by one” trap. If the requirement says “including both endpoints”, add one day to the exclusive difference.

from datetime import date

def inclusivedaycount(start: date, end: date) -> int:

if end < start:

raise ValueError("end must be >= start")

return (end – start).days + 1

if name == "main":

print(inclusivedaycount(date(2026, 2, 1), date(2026, 2, 3))) # 3

Duration difference (instants) (JavaScript)

When you truly mean elapsed time (like “how many seconds between these instants?”), measure in milliseconds since epoch.

function secondsBetweenInstants(a, b) {

// a and b are Date objects representing instants

const ms = b.getTime() – a.getTime();

return ms / 1000;

}

const start = new Date(‘2026-02-01T12:00:00Z‘);

const end = new Date(‘2026-02-01T12:00:45Z‘);

console.log(secondsBetweenInstants(start, end));

One practical note: Date is an instant type in JavaScript, but parsing without a Z or offset can behave differently across environments. If the string doesn’t include a zone, you’re no longer dealing with a pure instant—you‘re interpreting a local calendar time.

Parsing and Formatting: Be Strict on Input, Flexible on Output

Beginners often treat parsing as “string in, date out”. In real systems it’s closer to: “validate input, reject ambiguity, store a stable form”.

The three parsing rules I live by

1) Decide what formats you accept and document them.

2) Parse strictly. If the input doesn’t match, fail fast.

3) Normalize immediately. Convert to UTC instants or to date-only types depending on the task.

Strict ISO timestamp parsing (Python)

Python gives you multiple options. datetime.fromisoformat is convenient, but it’s not always strict across every variant you might see. For beginner tasks where you control the format, I like explicitly allowing a small set.

Here’s a small helper that accepts an ISO timestamp with an offset, including Z.

from future import annotations

from datetime import datetime, timezone

def parseisoinstant(value: str) -> datetime:

# Accept ‘Z‘ suffix for UTC.

if value.endswith(‘Z‘):

value = value[:-1] + ‘+00:00‘

dt = datetime.fromisoformat(value)

if dt.tzinfo is None:

raise ValueError("Timestamp must include timezone offset")

# Normalize to UTC

return dt.astimezone(timezone.utc)

if name == "main":

print(parseisoinstant("2026-02-01T18:22:05Z").isoformat())

print(parseisoinstant("2026-02-01T13:22:05-05:00").isoformat())

The “must include timezone offset” guard is a lifesaver. It prevents you from accidentally treating a local calendar time as an instant.

Strict date-only parsing (JavaScript)

If you want date-only, don’t feed YYYY-MM-DD into new Date() and assume it’ll behave like a pure date. It becomes an instant under the hood, and you’re back in time zone land.

Instead, parse and validate the components.

function parseIsoDateOnly(value) {

// Accept only YYYY-MM-DD

const m = /^\d{4}-\d{2}-\d{2}$/.exec(value);

if (!m) throw new Error(‘Invalid date format‘);

const [yearStr, monthStr, dayStr] = value.split(‘-‘);

const year = Number(yearStr);

const month = Number(monthStr);

const day = Number(dayStr);

if (!Number.isInteger(year) |

!Number.isInteger(month)

!Number.isInteger(day)) {

throw new Error(‘Invalid numeric components‘);

}

if (month 12) throw new Error(‘Month out of range‘);

if (day 31) throw new Error(‘Day out of range‘);

return { year, month, day }; // a plain calendar date

}

console.log(parseIsoDateOnly(‘2026-02-01‘));

This looks “too manual” at first, but for getting started it teaches the key lesson: sometimes you should keep a date as a date, not as a timestamp.

Output formatting: let users pick what they want

I’m strict on input, but on output I’m flexible. A single stored instant can be rendered as:

  • “Feb 1, 2026”
  • “2026-02-01”
  • “Sunday, February 1st”
  • “2026-02-01 10:22 AM” in a user’s locale/time zone

The best practice is: store a stable value, format at the edges.

Month and Week Problems: Where “Just Add 30 Days” Breaks

Beginner exercises often include “add N months” or “find the weekday” because it forces you to face irregular units.

Months are not a fixed duration

A month can be 28, 29, 30, or 31 days. So “add one month” is calendar logic, not duration logic.

If you’re implementing this for an interview, you need to define the rule. I usually pick:

  • Add months keeping the day-of-month when possible
  • If the target month doesn’t have that day, clamp to the last day of the target month

Example: Jan 31 + 1 month → Feb 28 (or Feb 29 in a leap year)

Here’s a clean implementation in Python using only the standard library.

from future import annotations

from dataclasses import dataclass

from datetime import date

import calendar

def addmonthsclamped(d: date, months: int) -> date:

if not isinstance(months, int):

raise TypeError("months must be int")

year = d.year + (d.month – 1 + months) // 12

month = (d.month – 1 + months) % 12 + 1

last_day = calendar.monthrange(year, month)[1]

day = min(d.day, last_day)

return date(year, month, day)

if name == "main":

print(addmonthsclamped(date(2026, 1, 31), 1)) # 2026-02-28

print(addmonthsclamped(date(2024, 1, 31), 1)) # 2024-02-29

print(addmonthsclamped(date(2026, 2, 1), 12)) # 2027-02-01

If you’re building production features (billing cycles, subscription renewals), prefer a dedicated date/time library that defines month arithmetic explicitly. But this is a solid “getting started” pattern.

Weekday and week-based calculations

Week problems come in two flavors:

  • “How many weeks is 10 days?” (fixed unit conversion)
  • “What day of the week is 2026-02-01?” (calendar logic)

In Python, date.weekday() returns Monday=0..Sunday=6.

from datetime import date

d = date(2026, 2, 1)

print(d.weekday()) # 6 means Sunday

If your problem statement cares about “ISO weekday” (Monday=1..Sunday=7), use isoweekday().

from datetime import date

d = date(2026, 2, 1)

print(d.isoweekday()) # 7 means Sunday

A common beginner bug is mixing these two systems and being off by one.

Scheduling: “Every Day at 9 AM” Is Not “Add 24 Hours Forever”

This is where the instant-vs-calendar model becomes real.

Scenario: daily reminders at 9:00 AM local time

What the user means:

  • “No matter what, I want my reminder when my clock says 9:00 AM.”

What many systems accidentally do:

  • Store the first reminder instant and repeatedly add 24 hours.

That fails across DST transitions because the day isn’t always 24 hours in local time.

A safer mental model

For a daily schedule you usually want:

  • Schedule definition: (time-of-day, timezone, recurrence rule)
  • Occurrence generation: compute the next local date, then map it to an instant

Even if you’re not implementing a full scheduling engine, this distinction helps you pick the right approach.

Ambiguous and nonexistent local times

When clocks change, some local times:

  • Do not exist (spring forward): e.g., 02:30 might never happen that day
  • Happen twice (fall back): e.g., 01:30 occurs two times with different offsets

If your system ever converts a local date-time + zone into an instant, you must choose a policy:

  • “Reject and ask user” (strict)
  • “Use the earlier occurrence”
  • “Use the later occurrence”

Beginner takeaway: if you’re seeing a one-hour drift “only on that Sunday”, you’re probably implicitly choosing a policy without knowing it.

Off-by-One Day Bugs: The Greatest Hits (and How I Debug Them)

If you work with dates long enough, you’ll eventually see:

  • “Why did this become yesterday?”
  • “Why did this become tomorrow?”
  • “Why do some users see Feb 1 and others see Jan 31?”

These are almost always boundary problems between date-only values and instants.

The most common cause

Someone stores a date-only value as a timestamp at midnight UTC (or midnight local), then renders it in a different time zone.

Example:

  • You mean: 2026-02-01 (a calendar date)
  • You store: 2026-02-01T00:00:00Z (an instant)
  • A user in America/Los_Angeles renders it as local time: 2026-01-31 4:00 PM (previous day)

This is not a time zone bug. It’s a data modeling bug.

My debugging checklist

When I hit an off-by-one-day issue, I ask these questions in order:

1) Is this value a date-only or an instant?

2) If it’s an instant, what is the exact stored value (ISO with offset or epoch)?

3) When rendering, what time zone is applied?

4) Is there any implicit conversion happening (like “parse this string into a Date”)?

5) Are we mixing “inclusive” and “exclusive” ranges?

If you’re getting started, you can prevent most of these by being disciplined about types and by storing date-only values as YYYY-MM-DD strings (or database date columns).

Practical Mini-Recipes (Beginner Problems, Production-Safe Patterns)

This section is where I’d spend my time if I were practicing for interviews or building confidence.

1) Validate a time-of-day string

Rules I typically use:

  • Accept HH:MM or HH:MM:SS
  • Reject hours outside 0..23, minutes/seconds outside 0..59

function validateTimeOfDay(value) {

const m = /^\d{2}:\d{2}(:\d{2})?$/.exec(value);

if (!m) return false;

const parts = value.split(‘:‘).map(Number);

const [hh, mm, ss] = [parts[0], parts[1], parts[2] ?? 0];

if (!Number.isInteger(hh) |

!Number.isInteger(mm)

!Number.isInteger(ss)) return false;

if (hh 23) return false;

if (mm 59) return false;

if (ss 59) return false;

return true;

}

console.log(validateTimeOfDay(‘09:00‘));

console.log(validateTimeOfDay(‘24:00‘));

2) Convert seconds to (hours, minutes, seconds)

This shows up constantly.

def secondstohms(total_seconds: int) -> tuple[int, int, int]:

if total_seconds < 0:

raise ValueError("total_seconds must be non-negative")

hours = total_seconds // 3600

remainder = total_seconds % 3600

minutes = remainder // 60

seconds = remainder % 60

return hours, minutes, seconds

if name == "main":

print(secondstohms(3661)) # (1, 1, 1)

3) Determine if two dates are in the same calendar week

This depends on the week definition. If you mean ISO weeks:

from datetime import date

def sameisoweek(a: date, b: date) -> bool:

return a.isocalendar()[:2] == b.isocalendar()[:2] # (isoyear, isoweek)

if name == "main":

print(sameisoweek(date(2026, 1, 1), date(2026, 1, 3)))

Beginner takeaway: “week of year” is surprisingly nuanced. ISO weeks can put early January dates into the last ISO week of the previous ISO year.

4) Compute “age” correctly

Age is a date-only problem.

from datetime import date

def ageon(birthdate: date, today: date) -> int:

if today < birth_date:

raise ValueError("today must be >= birth_date")

years = today.year – birth_date.year

hadbirthday = (today.month, today.day) >= (birthdate.month, birth_date.day)

return years if had_birthday else years – 1

if name == "main":

print(age_on(date(2000, 2, 2), date(2026, 2, 1)))

This avoids leap-year weirdness because it uses calendar comparisons, not “seconds since birth”.

Testing Date/Time Code: The Habits That Save You

If you’re new to date/time problems, here’s the honest truth: you don’t become “good at time zones” by reading docs. You become good by building a small test suite that forces your code to handle edge cases.

The edge cases I always include

For time-of-day conversions:

  • 12:00:00AM and 12:00:00PM
  • 00:00 and 23:59
  • Invalid inputs: wrong suffix, missing parts, out-of-range values

For dates:

  • Leap day: 2024-02-29
  • Century years: 1900 (not leap), 2000 (leap)
  • Month ends: Jan 31, Apr 30

For time zones:

  • At least one example around DST start
  • At least one example around DST end
  • At least one non-DST zone (to sanity check assumptions)

Freeze time (or inject a clock)

A common beginner mistake is testing “now” logic without controlling time. The fix is to pass a clock into your functions.

Instead of:

  • expires_at = datetime.now() + timedelta(minutes=15)

Prefer:

  • expires_at = now + timedelta(minutes=15) where now is a parameter

That single refactor makes your code testable.

A 2026-style workflow: AI-assisted test generation (safely)

I use AI as a test-case brainstorming partner, not as a source of truth.

My workflow:

1) I write the spec in plain language (“12:xx AM maps to 00:xx”).

2) I ask for candidate edge cases and invalid cases.

3) I manually verify the cases make sense.

4) I encode them into unit tests.

The key habit: AI can propose tests, but your program must pass tests you understand.

Performance Considerations (Without Premature Optimization)

Most beginner exercises don’t need performance work, but there are a few gotchas worth knowing.

Avoid repeated time zone conversions in tight loops

Time zone conversions can be more expensive than you expect because they consult rules and offsets.

If you’re processing a large batch:

  • Normalize instants once (e.g., to UTC)
  • Only convert to user time zones at the final formatting step

Prefer integer arithmetic for pure durations

If the problem is purely “seconds/minutes/hours”, keep it integer. Floating point time math is a source of tiny rounding bugs.

Keep ranges explicit

For “days between dates,” decide:

  • inclusive vs exclusive
  • whether the end is “up to” or “through”

Then code that explicitly. The performance is fine; the clarity is what matters.

A Beginner’s Checklist (Print This in Your Head)

When you’re stuck or you’re about to ship date/time code, run this checklist:

1) What is this value: instant or calendar?

2) Does it cross a boundary (API/DB/queue)? If yes, is it stored as:

– an instant in UTC, or

– a date-only value, or

– a local date-time plus an IANA time zone?

3) Is parsing strict? Are ambiguous inputs rejected?

4) Are you accidentally creating “naive” datetimes and treating them as global truth?

5) For “days between,” did you define inclusive vs exclusive?

6) For schedules, are you adding fixed durations when you actually need calendar logic?

7) Do tests cover leap years, month ends, and at least one DST boundary?

Closing: The Mental Model That Pays Off

Getting started with date and time problems is less about memorizing weird rules and more about respecting the two kinds of time:

  • Instants for timeline truth (logging, ordering, TTLs)
  • Calendar values for human intent (dates, times, schedules)

Once you build the habit of classifying the problem first, the code gets simpler. You stop fighting “mysterious” one-hour and one-day bugs because they stop being mysterious: they’re usually mismatches between representation and intent.

If you want to practice, pick one small function—like 12/24-hour conversion or days-between—and write tests that include the nasty edge cases. That’s the fastest path from “date/time problems feel cursed” to “I can reason about this confidently.”

Scroll to Top