Working with Time Zones, Currencies, and Coordinates in JavaScript: 6 Gotchas That Bite
If your app has users in more than one country, you'll eventually hit the same class of bugs: a meeting shows up an hour off, a price is wrong by a factor of 1000, or a "distance" is a few hundred kilometers out. These
If your app has users in more than one country, you'll eventually hit the same class of bugs: a meeting shows up an hour off, a price is wrong by a factor of 1000, or a "distance" is a few hundred kilometers out.
These problems aren't hard, but they're easy to get subtly wrong. Here are six gotchas I see repeatedly, with short JavaScript examples you can copy.
1. Store UTC, format at the edge
The most common time bug is storing local time. Store timestamps in UTC (or as Unix epoch) and convert only when displaying.
const meeting = new Date('2026-01-15T14:00:00Z');
const fmt = (tz) =>
new Intl.DateTimeFormat('en-US', {
timeZone: tz,
dateStyle: 'medium',
timeStyle: 'short',
}).format(meeting);
console.log(fmt('America/New_York')); // Jan 15, 2026, 9:00 AM
console.log(fmt('Europe/London')); // Jan 15, 2026, 2:00 PM
console.log(fmt('Asia/Dhaka')); // Jan 15, 2026, 8:00 PM
console.log(fmt('Asia/Tokyo')); // Jan 15, 2026, 11:00 PM
Intl.DateTimeFormat uses the IANA time zone database, so daylight saving rules are handled for you. No manual offset math.
Rule of thumb: never hardcode offsets like UTC-5. Use IANA names like America/New_York, because offsets change with daylight saving time and the name doesn't.
2. Know your timestamp formats
You'll switch between three formats constantly:
const d = new Date('2026-01-15T14:00:00Z');
d.toISOString(); // "2026-01-15T14:00:00.000Z"
d.getTime(); // 1768485600000 (milliseconds)
Math.floor(d.getTime() / 1000) // 1768485600 (Unix seconds)
The classic bug is mixing seconds and milliseconds. JavaScript uses milliseconds, while most APIs and databases use seconds. If a date shows up in 1970, you probably passed seconds where milliseconds were expected:
new Date(1768485600); // 1970-01-21... (wrong)
new Date(1768485600 * 1000); // 2026-01-15T14:00:00.000Z (right)
When debugging, a timestamp and date format converter is a quick way to sanity-check a value without opening a console.
3. "Add 30 days" is not "add 30 Γ 24 hours"
Across a daylight saving change, a local day can be 23 or 25 hours long. For calendar arithmetic in a specific zone, work with calendar dates, not raw milliseconds.
For business logic like deadlines and delivery estimates, you often want working days, not calendar days. That means skipping weekends and regional holidays:
function addBusinessDays(start, days) {
const d = new Date(start);
while (days > 0) {
d.setUTCDate(d.getUTCDate() + 1);
const day = d.getUTCDay(); // 0 = Sun, 6 = Sat
if (day !== 0 && day !== 6) days--;
}
return d;
}
This version ignores holidays, and holidays differ per country, which is where most real-world edge cases live. If you're validating your output, a business days calculator gives you a reference to compare against.
4. Currency: minor units and formatting
Two rules will save you from painful bugs.
Rule A: don't use floats for money. Store amounts as integers in the currency's smallest unit (cents, paise, etc.).
// Bad
0.1 + 0.2; // 0.30000000000000004
// Better: store 1999 for $19.99
const priceInCents = 1999;
Rule B: not every currency has two decimals. Japanese yen has 0 and Kuwaiti dinar has 3. Intl.NumberFormat knows this:
const money = (amount, currency, locale = 'en-US') =>
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);
money(1234.5, 'USD'); // $1,234.50
money(1234.5, 'JPY'); // Β₯1,235
money(1234.5, 'EUR', 'de-DE'); // 1.234,50 β¬
Use ISO 4217 codes (USD, EUR, BDT) in your data rather than symbols, since $ is used by many currencies. A currency codes reference and currency symbols list are handy when mapping user input.
A conversion is just a multiplication, but the rate source and timestamp matter:
function convert(amountMinor, rate) {
return Math.round(amountMinor * rate);
}
For quick checks while testing, a currency converter can confirm your numbers. For real payments, always use your payment provider's rate and fee data, because a public rate is informational.
5. Coordinates: decimal vs DMS
Maps APIs want decimal degrees, but users (and older documents) often give degrees-minutes-seconds. Here's a converter:
function toDMS(value, isLat) {
const dir = isLat ? (value >= 0 ? 'N' : 'S') : (value >= 0 ? 'E' : 'W');
const abs = Math.abs(value);
const deg = Math.floor(abs);
const minFloat = (abs - deg) * 60;
const min = Math.floor(minFloat);
const sec = ((minFloat - min) * 60).toFixed(2);
return `${deg}Β° ${min}' ${sec}" ${dir}`;
}
toDMS(51.5074, true); // 51Β° 30' 26.64" N
toDMS(-0.1278, false); // 0Β° 7' 40.08" W
Common pitfall: swapping latitude and longitude. Latitude is the vertical axis (Β±90), longitude the horizontal (Β±180). GeoJSON uses [longitude, latitude], while many APIs use lat, lng. Validate ranges and you'll catch most mix-ups. A coordinate converter is useful for spot checks.
6. Distance: use the haversine formula
Straight-line distance on a sphere (the "great-circle" distance) is easy with the haversine formula:
function haversineKm(lat1, lon1, lat2, lon2) {
const R = 6371; // mean Earth radius in km
const toRad = (x) => (x * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.asin(Math.sqrt(a));
}
// London -> New York
haversineKm(51.5074, -0.1278, 40.7128, -74.006); // ~5570 km
It's accurate enough for most apps (within roughly 0.5% because Earth isn't a perfect sphere). It's not driving distance, which needs a routing service. To verify results, try a distance calculator with the same two points.
Quick checklist
- Store UTC; format with IANA time zone names at the edge.
- Watch seconds vs milliseconds.
- Treat calendar days, hours, and business days as different things.
- Store money as integer minor units, with ISO 4217 codes.
- Don't assume two decimals for every currency.
- Validate lat/lng ranges and axis order.
- Haversine for straight-line distance; routing APIs for real travel distance.
Handy free references
When I'm debugging or writing tests, I keep a few browser-based utilities open for quick verification instead of writing throwaway scripts. The World & Global Tools hub on Noloii groups time zone, currency, geography, and weather utilities in one place, free and with no install.
What's the nastiest time zone or currency bug you've shipped? Share it in the comments.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.