12-Hour vs 24-Hour - Regional Conventions
The most basic format choice is 12-hour (AM/PM) versus 24-hour. The 12-hour clock dominates everyday use in the U.S., Canada, Australia, and the Philippines. The 24-hour clock is the norm in continental Europe, Japan, Korea, and China. The U.K. occupies a middle position: spoken English uses 12-hour, but train timetables and official documents use 24-hour.
The right software design defers to the user's locale rather than picking a global default. A server hardcoding "14:30" will look wrong to American users; "2:30 PM" will look pedantic to those used to the 24-hour clock. Honor the OS or browser locale and let the formatting layer choose, rather than baking a choice into business logic.
ISO 8601 - Machine-Readable, Not Human-Readable
ISO 8601 (e.g., 2026-05-15T16:30:00+09:00) is excellent for machine-to-machine data exchange but should not be presented directly to end users. Use it for API payloads, database storage, and log output, then convert to a localized human-readable form at the display layer. Mixing roles between machine and human formats is the single most common cause of user complaints about timestamps.
There is one exception: technical dashboards and debug screens for engineers. The explicit time zone offset in ISO 8601 removes ambiguity when comparing events across regions. Whether your audience is technical or general determines whether ISO 8601 is acceptable in the user-facing path.
JavaScript's Intl.DateTimeFormat API provides locale-aware formatting using the OS's internationalization data. new Intl.DateTimeFormat('ja-JP', { hour: '2-digit', minute: '2-digit' }).format(date) returns "16:30," while changing the locale to 'en-US' yields "4:30 PM." No third-party library required, and the data stays current with OS updates.
Watch for two pitfalls. First, dateStyle/timeStyle and individual options (year/month/day/hour/minute) cannot be mixed in the same call. Second, browsers vary slightly in output details such as whether spaces appear around AM/PM, so snapshot tests can be flaky. Use string-includes assertions or normalize the output if you need stable test fixtures.
Relative Time - The "3 Minutes Ago" Pattern
Social and chat apps commonly display "3 minutes ago" or "yesterday" rather than absolute times. The intuition is simple, but several design choices follow. How granular should sub-minute display be (most services collapse to "just now" under a minute)? How many days back before switching to absolute dates (Twitter switches at 24 hours, Facebook at 7 days)? How is "yesterday" computed when the user crosses a time zone?
Intl.RelativeTimeFormat handles the localization of the resulting string but does not pick the appropriate unit (second, minute, hour, day, week, month, year). You still need a small piece of logic to select the right unit based on the magnitude of the difference. Several libraries (date-fns, Day.js relativeTime plugin) wrap this for you, but the underlying decisions are still yours to make.
Edge Cases - 12:00 AM and 12:00 PM
The 12-hour clock has a famous ambiguity at noon and midnight. By convention, 12:00 PM is noon and 12:00 AM is midnight, but this is logically inconsistent (PM stands for post meridiem, after noon, yet noon itself is labeled PM). The aviation industry sidesteps the issue by using 24-hour notation (00:00 for midnight, 12:00 for noon), and U.S. legal documents typically write "12:00 noon" or "12:00 midnight" explicitly.
The defensive approach in software is to store times internally as 24-hour integers (0-23) and convert only at display. When accepting user input in 12-hour form, validate the AM/PM interpretation explicitly and include 12:00 AM and 12:00 PM in test cases. Bugs at the noon/midnight boundary are easy to introduce and easy to miss in casual testing.