clockwork-tz
Version:
Accurate timezone conversions and DST handling with optional deterministic IANA tzdb support
351 lines (250 loc) • 9.56 kB
Markdown
//badge.fury.io/js/clockwork-tz.svg)](https://badge.fury.io/js/clockwork-tz)
[](https://github.com/adamturner/clockwork-tz/actions)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
A comprehensive timezone library for JavaScript and TypeScript applications. Provides accurate timezone conversions with automatic DST handling, supporting both system-based and deterministic timezone calculations.
- **Accurate conversions** - Handles unusual timezone offsets (+10:30, +8:45, +5:45) and complex DST rules
- **Framework agnostic** - Works in Node.js, browsers, React Native, and Electron
- **Dual engine architecture** - System Intl API (default) or optional embedded IANA tzdb for consistency
- **TypeScript support** - Complete type definitions with strict typing
- **Real-time capabilities** - DST transition watching and live clock utilities
- **Zero dependencies** - Default engine uses platform APIs only
- **Universal builds** - ESM and CommonJS with tree-shaking support
## Installation
```bash
npm install clockwork-tz
```
## Quick Start
```javascript
import { initClockworkTZ, fromUTC, convert, format } from 'clockwork-tz'
// Initialize (optional - uses system timezone data by default)
initClockworkTZ()
// Convert UTC to any timezone
const result = fromUTC('2024-07-15T14:00:00.000Z', 'Australia/Lord_Howe')
console.log(result.local) // "2024-07-16 01:00:00.000"
console.log(result.offsetMinutes) // 660 (+11:00 DST)
console.log(result.isDST) // true
// Convert between timezones
const converted = convert('2024-07-15T16:30:00', 'Asia/Kathmandu', 'America/St_Johns')
console.log(converted.local) // "2024-07-15 06:45:00.000"
// Format with patterns
const formatted = format(new Date(), 'Pacific/Chatham', 'yyyy-MM-dd HH:mm zzz')
console.log(formatted) // "2024-07-16 03:45 +1345"
```
Uses platform `Intl.DateTimeFormat` and `date-fns-tz` for timezone calculations:
- No external data files
- Automatic OS/browser updates
- Suitable for most applications
Uses a specific IANA timezone database version for deterministic results:
- Consistent across all platforms
- Optional CDN updates with verification
- Ideal for applications requiring exact reproducibility
```javascript
initClockworkTZ({
engine: 'embedded-tzdb',
manifestURL: 'https://cdn.example.com/tzdb-manifest.json',
autoUpdateIntervalMs: 24 * 60 * 60 * 1000, // Daily updates
})
```
## API Reference
### Core Functions
#### `fromUTC(input, zone, options?)`
Convert UTC time to a specific timezone.
```javascript
const result = fromUTC('2024-07-15T14:00:00Z', 'Australia/Lord_Howe')
// Returns ClockworkTime object with local time, offset, DST status
```
Interpret a local time string in a specific timezone with disambiguation for DST transitions.
```javascript
// Handle ambiguous time during fall-back transition
const result = interpretLocal('2024-11-03T01:30:00', 'America/New_York', {
disambiguation: 'latest', // 'earliest', 'latest', or 'reject'
})
```
Convert time from one timezone to another.
```javascript
const result = convert('2024-07-15T14:30:00', 'Asia/Kathmandu', 'Pacific/Chatham')
```
Format a date/time in a specific timezone using date-fns patterns.
```javascript
const formatted = format(new Date(), 'Europe/London', 'yyyy-MM-dd HH:mm zzz')
```
```javascript
const offset = getOffset(new Date(), 'Australia/Eucla') // 525 minutes (+8:45)
const inDST = isDST(new Date(), 'America/New_York') // true/false
```
```javascript
const zones = listTimeZones({ labelStyle: 'offset' })
// Returns array of { value: string, label: string, offsetMinutes: number }
```
```javascript
const stopTicking = onTick(now => {
console.log('Current time:', format(now, 'UTC'))
}, 1000)
// Later: stopTicking()
```
```javascript
const stopWatching = watchZoneTransitions('America/New_York', transitionDate => {
console.log('DST transition at:', transitionDate)
})
```
The library properly handles problematic times during DST transitions:
```javascript
try {
// This time doesn't exist (2 AM becomes 3 AM)
interpretLocal('2024-03-10T02:30:00', 'America/New_York', {
disambiguation: 'reject',
})
} catch (error) {
console.log(error instanceof InvalidTimeError) // true
}
// Or handle gracefully
const result = interpretLocal('2024-03-10T02:30:00', 'America/New_York', {
disambiguation: 'latest', // Use time after the gap
})
```
```javascript
// This time occurs twice (1:30 AM happens twice)
const first = interpretLocal('2024-11-03T01:30:00', 'America/New_York', {
disambiguation: 'earliest', // First occurrence (DST)
})
const second = interpretLocal('2024-11-03T01:30:00', 'America/New_York', {
disambiguation: 'latest', // Second occurrence (standard)
})
console.log(first.isDST) // true
console.log(second.isDST) // false
```
The library handles timezone offsets that often cause issues in other libraries:
```javascript
// Lord Howe Island: +10:30 standard, +11:00 DST (30-minute shift)
const lordHowe = fromUTC('2024-01-15T12:00:00Z', 'Australia/Lord_Howe')
console.log(lordHowe.offsetMinutes) // 660 (+11:00)
// Eucla: Fixed +8:45 offset
const eucla = fromUTC('2024-07-15T12:00:00Z', 'Australia/Eucla')
console.log(eucla.offsetMinutes) // 525 (+8:45)
// Nepal: +5:45 offset
const kathmandu = fromUTC('2024-07-15T12:00:00Z', 'Asia/Kathmandu')
console.log(kathmandu.offsetMinutes) // 345 (+5:45)
// Chatham Islands: +12:45 standard, +13:45 DST
const chatham = fromUTC('2024-01-15T12:00:00Z', 'Pacific/Chatham')
console.log(chatham.offsetMinutes) // 825 (+13:45)
// Newfoundland: -3:30 standard, -2:30 DST
const stjohns = fromUTC('2024-07-15T12:00:00Z', 'America/St_Johns')
console.log(stjohns.offsetMinutes) // -150 (-2:30)
```
```javascript
const { initClockworkTZ, fromUTC } = require('clockwork-tz')
// or
import { initClockworkTZ, fromUTC } from 'clockwork-tz'
```
```html
<script type="module">
import { initClockworkTZ, fromUTC } from 'https://cdn.skypack.dev/clockwork-tz'
</script>
```
```javascript
import { initClockworkTZ, onTick } from 'clockwork-tz'
// React hook example
function useWorldClock(zones) {
const [times, setTimes] = useState({})
useEffect(() => {
return onTick(now => {
const newTimes = {}
zones.forEach(zone => {
newTimes[zone] = fromUTC(now, zone)
})
setTimes(newTimes)
}, 1000)
}, [zones])
return times
}
```
Built-in format patterns for common use cases:
```javascript
import { FORMAT_PATTERNS, formatWithPattern } from 'clockwork-tz'
const date = new Date()
const zone = 'Europe/Paris'
formatWithPattern(date, zone, 'default') // "2024-07-15 16:30:00 CEST"
formatWithPattern(date, zone, 'human') // "July 15, 2024 at 4:30 PM CEST"
formatWithPattern(date, zone, 'iso8601') // "2024-07-15T16:30:00+02:00"
formatWithPattern(date, zone, 'rfc2822') // "Mon, 15 Jul 2024 16:30:00 +0200"
```
Complete TypeScript definitions included:
```typescript
import type { ClockworkTime, Disambiguation, EngineKind } from 'clockwork-tz'
interface ClockworkTime {
utc: string
zone: string
local: string
offsetMinutes: number
isDST: boolean
source: EngineKind
tzdbVersion?: string
}
```
```bash
npm test
npm run test:watch
npm run test:coverage
```
The test suite covers:
- Unusual timezone offset calculations
- DST transition edge cases
- Ambiguous time handling
- Round-trip conversion accuracy
- Historical date processing
| Engine | Gzipped | Notes |
| ------------- | ------- | --------------------------- |
| system-intl | ~15KB | Uses platform timezone data |
| embedded-tzdb | ~45KB | Includes IANA tzdb subset |
Tree-shaking friendly - only import what you use.
Contributions are welcome. Please read the contributing guidelines and ensure all tests pass before submitting a pull request.
MIT License - see LICENSE file for details.
- Future timezone rule changes require updates
- Historical accuracy limited by platform data (pre-1970)
- JavaScript Date limitations (no leap second support)
- Some unusual timezone identifiers may not be supported by system engine
## Migration
### From moment-timezone
```javascript
// Before
moment.tz('2024-07-15 14:00', 'America/New_York').format()
// After
format('2024-07-15T14:00:00', 'America/New_York')
```
### From date-fns-tz
```javascript
// Before
formatInTimeZone(date, 'Europe/London', 'yyyy-MM-dd HH:mm zzz')
// After
format(date, 'Europe/London', 'yyyy-MM-dd HH:mm zzz')
```
[![npm version](https: