@gobrand/tiempo 2.3.4 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -978
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,998 +4,43 @@
|
|
|
4
4
|
[](https://github.com/go-brand/tiempo/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Timezone conversions that don't suck.** Built on the [Temporal API](https://tc39.es/proposal-temporal/docs/).
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
npm install @gobrand/tiempo
|
|
13
|
-
# or
|
|
14
|
-
pnpm add @gobrand/tiempo
|
|
15
|
-
# or
|
|
16
|
-
yarn add @gobrand/tiempo
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Why tiempo?
|
|
20
|
-
|
|
21
|
-
The Temporal API is powerful but requires understanding its various methods and objects. **tiempo** (`@gobrand/tiempo`) provides intuitive utilities for every datetime task:
|
|
22
|
-
|
|
23
|
-
- **🌍 Timezone conversions** - Convert between UTC and any timezone effortlessly
|
|
24
|
-
- **➕ Complete arithmetic** - Add/subtract any time unit from nanoseconds to years
|
|
25
|
-
- **📅 Calendar operations** - Start/end of day, week, month, year with DST handling
|
|
26
|
-
- **🔍 Comparisons** - Check if dates are before, after, same day, future, or past
|
|
27
|
-
- **📊 Differences** - Calculate precise differences in any time unit
|
|
28
|
-
- **🎨 Formatting** - Format dates with date-fns-style tokens or Intl
|
|
29
|
-
- **⚡️ Type-safe** - Full TypeScript support with proper Temporal types
|
|
30
|
-
- **🎯 Zero config** - Simple, direct function signatures
|
|
31
|
-
|
|
32
|
-
**Key features:**
|
|
33
|
-
- ✅ Native timezone support with Temporal API
|
|
34
|
-
- ✅ DST transitions handled automatically
|
|
35
|
-
- ✅ Nanosecond precision (beyond milliseconds)
|
|
36
|
-
- ✅ Calendar-aware arithmetic (leap years, month-end dates)
|
|
37
|
-
- ✅ Familiar date-fns-style API, built for the future
|
|
38
|
-
|
|
39
|
-
**Perfect for:**
|
|
40
|
-
- Social media scheduling apps
|
|
41
|
-
- Calendar applications
|
|
42
|
-
- Booking systems
|
|
43
|
-
- Time tracking tools
|
|
44
|
-
- Analytics dashboards
|
|
45
|
-
- Any app that needs to handle user timezones
|
|
46
|
-
|
|
47
|
-
## Quick Start
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { toZonedTime, toUtcString, toUtc, toDate } from '@gobrand/tiempo';
|
|
51
|
-
|
|
52
|
-
// From ISO string (typical backend API)
|
|
53
|
-
const zoned = toZonedTime("2025-01-20T20:00:00.000Z", "America/New_York");
|
|
54
|
-
console.log(zoned.hour); // 15 (3 PM in New York)
|
|
55
|
-
|
|
56
|
-
// From Date object (e.g., Drizzle ORM)
|
|
57
|
-
const date = new Date("2025-01-20T20:00:00.000Z");
|
|
58
|
-
const zonedFromDate = toZonedTime(date, "America/New_York");
|
|
59
|
-
console.log(zonedFromDate.hour); // 15 (3 PM in New York)
|
|
60
|
-
|
|
61
|
-
// Back to ISO string
|
|
62
|
-
const utcString = toUtcString(zoned);
|
|
63
|
-
console.log(utcString); // "2025-01-20T20:00:00Z"
|
|
64
|
-
|
|
65
|
-
// Back to Date object (for Drizzle ORM)
|
|
66
|
-
const backToDate = toDate(zoned);
|
|
67
|
-
console.log(backToDate.toISOString()); // "2025-01-20T20:00:00.000Z"
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## API
|
|
71
|
-
|
|
72
|
-
### Core Conversions
|
|
73
|
-
|
|
74
|
-
#### `toZonedTime(input, timezone)`
|
|
75
|
-
|
|
76
|
-
Convert a UTC ISO string, Date, Instant, or ZonedDateTime to a ZonedDateTime in the specified timezone.
|
|
77
|
-
|
|
78
|
-
**Parameters:**
|
|
79
|
-
- `input` (string | Date | Temporal.Instant | Temporal.ZonedDateTime): A UTC ISO 8601 string, Date object, Temporal.Instant, or Temporal.ZonedDateTime
|
|
80
|
-
- `timezone` (string): An IANA timezone identifier (e.g., `"America/New_York"`, `"Europe/London"`)
|
|
81
|
-
|
|
82
|
-
**Returns:** `Temporal.ZonedDateTime` - The same instant in the specified timezone
|
|
83
|
-
|
|
84
|
-
**Example:**
|
|
85
|
-
```typescript
|
|
86
|
-
import { toZonedTime } from '@gobrand/tiempo';
|
|
87
|
-
|
|
88
|
-
// From ISO string
|
|
89
|
-
const zoned = toZonedTime("2025-01-20T20:00:00.000Z", "America/New_York");
|
|
90
|
-
console.log(zoned.hour); // 15 (3 PM in New York)
|
|
91
|
-
console.log(zoned.toString()); // "2025-01-20T15:00:00-05:00[America/New_York]"
|
|
92
|
-
|
|
93
|
-
// From Date (e.g., from Drizzle ORM)
|
|
94
|
-
const date = new Date("2025-01-20T20:00:00.000Z");
|
|
95
|
-
const zoned2 = toZonedTime(date, "America/New_York");
|
|
96
|
-
console.log(zoned2.hour); // 15 (3 PM in New York)
|
|
97
|
-
|
|
98
|
-
// From Instant
|
|
99
|
-
const instant = Temporal.Instant.from("2025-01-20T20:00:00Z");
|
|
100
|
-
const zoned3 = toZonedTime(instant, "Asia/Tokyo");
|
|
101
|
-
|
|
102
|
-
// From ZonedDateTime (convert to different timezone)
|
|
103
|
-
const nyTime = Temporal.ZonedDateTime.from("2025-01-20T15:00:00-05:00[America/New_York]");
|
|
104
|
-
const tokyoTime = toZonedTime(nyTime, "Asia/Tokyo");
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
#### `toUtc(input)`
|
|
108
|
-
|
|
109
|
-
Convert a UTC ISO string, Date, or ZonedDateTime to a Temporal.Instant (UTC).
|
|
110
|
-
|
|
111
|
-
**Parameters:**
|
|
112
|
-
- `input` (string | Date | Temporal.ZonedDateTime): A UTC ISO 8601 string, Date object, or Temporal.ZonedDateTime
|
|
113
|
-
|
|
114
|
-
**Returns:** `Temporal.Instant` - A Temporal.Instant representing the same moment in UTC
|
|
115
|
-
|
|
116
|
-
**Example:**
|
|
117
|
-
```typescript
|
|
118
|
-
import { toUtc } from '@gobrand/tiempo';
|
|
119
|
-
|
|
120
|
-
// From ISO string
|
|
121
|
-
const instant = toUtc("2025-01-20T20:00:00.000Z");
|
|
122
|
-
|
|
123
|
-
// From Date (e.g., from Drizzle ORM)
|
|
124
|
-
const date = new Date("2025-01-20T20:00:00.000Z");
|
|
125
|
-
const instant2 = toUtc(date);
|
|
126
|
-
|
|
127
|
-
// From ZonedDateTime
|
|
128
|
-
const zoned = Temporal.ZonedDateTime.from("2025-01-20T15:00:00-05:00[America/New_York]");
|
|
129
|
-
const instant3 = toUtc(zoned);
|
|
130
|
-
// All represent the same UTC moment: 2025-01-20T20:00:00Z
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
#### `toUtcString(input)`
|
|
134
|
-
|
|
135
|
-
Convert a Temporal.Instant or ZonedDateTime to a UTC ISO 8601 string.
|
|
136
|
-
|
|
137
|
-
**Parameters:**
|
|
138
|
-
- `input` (Temporal.Instant | Temporal.ZonedDateTime): A Temporal.Instant or Temporal.ZonedDateTime
|
|
139
|
-
|
|
140
|
-
**Returns:** `string` - A UTC ISO 8601 string representation
|
|
141
|
-
|
|
142
|
-
**Example:**
|
|
143
|
-
```typescript
|
|
144
|
-
import { toUtcString } from '@gobrand/tiempo';
|
|
145
|
-
|
|
146
|
-
// From ZonedDateTime
|
|
147
|
-
const zoned = Temporal.ZonedDateTime.from("2025-01-20T15:00:00-05:00[America/New_York]");
|
|
148
|
-
const iso = toUtcString(zoned);
|
|
149
|
-
console.log(iso); // "2025-01-20T20:00:00Z"
|
|
150
|
-
|
|
151
|
-
// From Instant
|
|
152
|
-
const instant = Temporal.Instant.from("2025-01-20T20:00:00Z");
|
|
153
|
-
const iso2 = toUtcString(instant);
|
|
154
|
-
console.log(iso2); // "2025-01-20T20:00:00Z"
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### `toDate(input)`
|
|
158
|
-
|
|
159
|
-
Convert a Temporal.Instant or ZonedDateTime to a Date object.
|
|
160
|
-
|
|
161
|
-
**Parameters:**
|
|
162
|
-
- `input` (Temporal.Instant | Temporal.ZonedDateTime): A Temporal.Instant or Temporal.ZonedDateTime
|
|
163
|
-
|
|
164
|
-
**Returns:** `Date` - A Date object representing the same moment in time
|
|
165
|
-
|
|
166
|
-
**Example:**
|
|
167
|
-
```typescript
|
|
168
|
-
import { toDate } from '@gobrand/tiempo';
|
|
169
|
-
|
|
170
|
-
// From Instant
|
|
171
|
-
const instant = Temporal.Instant.from("2025-01-20T20:00:00Z");
|
|
172
|
-
const date = toDate(instant);
|
|
173
|
-
console.log(date.toISOString()); // "2025-01-20T20:00:00.000Z"
|
|
174
|
-
|
|
175
|
-
// From ZonedDateTime
|
|
176
|
-
const zoned = Temporal.ZonedDateTime.from("2025-01-20T15:00:00-05:00[America/New_York]");
|
|
177
|
-
const date2 = toDate(zoned);
|
|
178
|
-
console.log(date2.toISOString()); // "2025-01-20T20:00:00.000Z"
|
|
179
|
-
|
|
180
|
-
// Use with Drizzle ORM (for storing back to database)
|
|
181
|
-
const instant = Temporal.Instant.from("2025-01-20T20:00:00Z");
|
|
182
|
-
const dateForDb = toDate(instant);
|
|
183
|
-
await db.update(posts).set({ publishedAt: dateForDb });
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Formatting
|
|
187
|
-
|
|
188
|
-
#### `intlFormatDistance(laterDate, earlierDate, options?)`
|
|
189
|
-
|
|
190
|
-
Format the distance between two dates as a human-readable, internationalized string using `Intl.RelativeTimeFormat`. Automatically selects the most appropriate unit (seconds, minutes, hours, days, weeks, months, years) based on the distance.
|
|
191
|
-
|
|
192
|
-
**Parameters:**
|
|
193
|
-
- `laterDate` (Temporal.Instant | Temporal.ZonedDateTime): The later date to compare
|
|
194
|
-
- `earlierDate` (Temporal.Instant | Temporal.ZonedDateTime): The earlier date to compare with
|
|
195
|
-
- `options` (IntlFormatDistanceOptions, optional): Formatting options
|
|
196
|
-
- `unit` (Intl.RelativeTimeFormatUnit): Force a specific unit instead of automatic selection
|
|
197
|
-
- `locale` (string | string[]): Locale for formatting (default: system locale)
|
|
198
|
-
- `numeric` ('always' | 'auto'): Use numeric values always or allow natural language like "tomorrow" (default: 'auto')
|
|
199
|
-
- `style` ('long' | 'short' | 'narrow'): Formatting style (default: 'long')
|
|
200
|
-
- `localeMatcher` ('best fit' | 'lookup'): Locale matching algorithm
|
|
201
|
-
|
|
202
|
-
**Returns:** `string` - Formatted relative time string
|
|
203
|
-
|
|
204
|
-
**Automatic unit selection:**
|
|
205
|
-
- < 60 seconds → "in X seconds" / "X seconds ago"
|
|
206
|
-
- < 60 minutes → "in X minutes" / "X minutes ago"
|
|
207
|
-
- < 24 hours → "in X hours" / "X hours ago"
|
|
208
|
-
- < 7 days → "tomorrow" / "yesterday" / "in X days" / "X days ago"
|
|
209
|
-
- < 4 weeks → "next week" / "last week" / "in X weeks" / "X weeks ago"
|
|
210
|
-
- < 12 months → "next month" / "last month" / "in X months" / "X months ago"
|
|
211
|
-
- ≥ 12 months → "next year" / "last year" / "in X years" / "X years ago"
|
|
212
|
-
|
|
213
|
-
**Example:**
|
|
214
|
-
```typescript
|
|
215
|
-
import { intlFormatDistance } from '@gobrand/tiempo';
|
|
216
|
-
|
|
217
|
-
const later = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
218
|
-
const earlier = Temporal.Instant.from('2025-01-20T11:00:00Z');
|
|
219
|
-
|
|
220
|
-
// Basic usage
|
|
221
|
-
intlFormatDistance(later, earlier);
|
|
222
|
-
// => "in 1 hour"
|
|
223
|
-
|
|
224
|
-
intlFormatDistance(earlier, later);
|
|
225
|
-
// => "1 hour ago"
|
|
226
|
-
|
|
227
|
-
// With different locales
|
|
228
|
-
intlFormatDistance(later, earlier, { locale: 'es' });
|
|
229
|
-
// => "dentro de 1 hora"
|
|
230
|
-
|
|
231
|
-
intlFormatDistance(later, earlier, { locale: 'ja' });
|
|
232
|
-
// => "1 時間後"
|
|
233
|
-
|
|
234
|
-
// Force numeric format
|
|
235
|
-
const tomorrow = Temporal.Instant.from('2025-01-21T00:00:00Z');
|
|
236
|
-
const today = Temporal.Instant.from('2025-01-20T00:00:00Z');
|
|
237
|
-
|
|
238
|
-
intlFormatDistance(tomorrow, today, { numeric: 'auto' });
|
|
239
|
-
// => "tomorrow"
|
|
240
|
-
|
|
241
|
-
intlFormatDistance(tomorrow, today, { numeric: 'always' });
|
|
242
|
-
// => "in 1 day"
|
|
243
|
-
|
|
244
|
-
// Different styles
|
|
245
|
-
const future = Temporal.Instant.from('2027-01-20T00:00:00Z');
|
|
246
|
-
const now = Temporal.Instant.from('2025-01-20T00:00:00Z');
|
|
247
|
-
|
|
248
|
-
intlFormatDistance(future, now, { style: 'long' });
|
|
249
|
-
// => "in 2 years"
|
|
250
|
-
|
|
251
|
-
intlFormatDistance(future, now, { style: 'short' });
|
|
252
|
-
// => "in 2 yr."
|
|
253
|
-
|
|
254
|
-
intlFormatDistance(future, now, { style: 'narrow' });
|
|
255
|
-
// => "in 2y"
|
|
256
|
-
|
|
257
|
-
// Force specific units
|
|
258
|
-
intlFormatDistance(future, now, { unit: 'quarter' });
|
|
259
|
-
// => "in 8 quarters"
|
|
260
|
-
|
|
261
|
-
const dayLater = Temporal.Instant.from('2025-01-21T00:00:00Z');
|
|
262
|
-
intlFormatDistance(dayLater, today, { unit: 'hour' });
|
|
263
|
-
// => "in 24 hours"
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
#### `format(input, formatStr, options?)`
|
|
267
|
-
|
|
268
|
-
Format a Temporal.Instant or ZonedDateTime using date-fns-like format tokens.
|
|
269
|
-
|
|
270
|
-
**Parameters:**
|
|
271
|
-
- `input` (Temporal.Instant | Temporal.ZonedDateTime): A Temporal.Instant or Temporal.ZonedDateTime to format
|
|
272
|
-
- `formatStr` (string): Format string using date-fns tokens (e.g., "yyyy-MM-dd HH:mm:ss")
|
|
273
|
-
- `options` (FormatOptions, optional): Configuration for locale and timezone
|
|
274
|
-
- `locale` (string): BCP 47 language tag (default: "en-US")
|
|
275
|
-
- `timeZone` (string): IANA timezone identifier to convert to before formatting
|
|
276
|
-
|
|
277
|
-
**Returns:** `string` - Formatted date string
|
|
278
|
-
|
|
279
|
-
**Supported tokens:**
|
|
280
|
-
- **Year**: `yyyy` (2025), `yy` (25), `y` (2025)
|
|
281
|
-
- **Month**: `MMMM` (January), `MMM` (Jan), `MM` (01), `M` (1), `Mo` (1st)
|
|
282
|
-
- **Day**: `dd` (20), `d` (20), `do` (20th)
|
|
283
|
-
- **Weekday**: `EEEE` (Monday), `EEE` (Mon), `EEEEE` (M)
|
|
284
|
-
- **Hour**: `HH` (15, 24h), `H` (15), `hh` (03, 12h), `h` (3)
|
|
285
|
-
- **Minute**: `mm` (30), `m` (30)
|
|
286
|
-
- **Second**: `ss` (45), `s` (45)
|
|
287
|
-
- **AM/PM**: `a` (PM), `aa` (PM), `aaa` (pm), `aaaa` (p.m.), `aaaaa` (p)
|
|
288
|
-
- **Timezone**: `xxx` (-05:00), `xx` (-0500), `XXX` (Z or -05:00), `zzzz` (Eastern Standard Time)
|
|
289
|
-
- **Quarter**: `Q` (1), `QQQ` (Q1), `QQQQ` (1st quarter)
|
|
290
|
-
- **Milliseconds**: `SSS` (123)
|
|
291
|
-
- **Timestamp**: `T` (milliseconds), `t` (seconds)
|
|
292
|
-
- **Escape text**: Use single quotes `'...'`, double single quotes `''` for literal quote
|
|
293
|
-
|
|
294
|
-
**Example:**
|
|
295
|
-
```typescript
|
|
296
|
-
import { format, toZonedTime, toUtc } from '@gobrand/tiempo';
|
|
297
|
-
|
|
298
|
-
// From ISO string to ZonedDateTime, then format
|
|
299
|
-
const isoString = "2025-01-20T20:30:45.000Z";
|
|
300
|
-
const zoned = toZonedTime(isoString, "America/New_York");
|
|
301
|
-
|
|
302
|
-
format(zoned, "yyyy-MM-dd"); // "2025-01-20"
|
|
303
|
-
format(zoned, "MMMM d, yyyy"); // "January 20, 2025"
|
|
304
|
-
format(zoned, "h:mm a"); // "3:30 PM"
|
|
305
|
-
format(zoned, "EEEE, MMMM do, yyyy 'at' h:mm a"); // "Monday, January 20th, 2025 at 3:30 PM"
|
|
306
|
-
|
|
307
|
-
// With locale
|
|
308
|
-
format(zoned, "MMMM d, yyyy", { locale: "es-ES" }); // "enero 20, 2025"
|
|
309
|
-
format(zoned, "EEEE", { locale: "fr-FR" }); // "lundi"
|
|
310
|
-
|
|
311
|
-
// From ISO string to Instant (UTC), then format with timezone conversion
|
|
312
|
-
const instant = toUtc(isoString);
|
|
313
|
-
format(instant, "yyyy-MM-dd HH:mm", { timeZone: "America/New_York" }); // "2025-01-20 15:30"
|
|
314
|
-
format(instant, "yyyy-MM-dd HH:mm", { timeZone: "Asia/Tokyo" }); // "2025-01-21 05:30"
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
#### `formatPlainDate(date, formatStr, options?)`
|
|
318
|
-
|
|
319
|
-
Format a Temporal.PlainDate using date-fns-like format tokens. Only supports date-related tokens (no time or timezone tokens).
|
|
320
|
-
|
|
321
|
-
**Parameters:**
|
|
322
|
-
- `date` (Temporal.PlainDate): A Temporal.PlainDate to format
|
|
323
|
-
- `formatStr` (string): Format string using date-fns tokens (e.g., "yyyy-MM-dd")
|
|
324
|
-
- `options` (FormatPlainDateOptions, optional): Configuration for locale
|
|
325
|
-
- `locale` (string): BCP 47 language tag (default: "en-US")
|
|
326
|
-
|
|
327
|
-
**Returns:** `string` - Formatted date string
|
|
328
|
-
|
|
329
|
-
**Supported tokens:**
|
|
330
|
-
- **Year**: `yyyy` (2025), `yy` (25), `y` (2025)
|
|
331
|
-
- **Month**: `MMMM` (January), `MMM` (Jan), `MM` (01), `M` (1), `Mo` (1st)
|
|
332
|
-
- **Day**: `dd` (20), `d` (20), `do` (20th)
|
|
333
|
-
- **Weekday**: `EEEE` (Monday), `EEE` (Mon), `EEEEE` (M)
|
|
334
|
-
- **Quarter**: `Q` (1), `QQQ` (Q1), `QQQQ` (1st quarter)
|
|
335
|
-
- **Era**: `G` (AD), `GGGG` (Anno Domini)
|
|
336
|
-
- **Escape text**: Use single quotes `'...'`, double single quotes `''` for literal quote
|
|
337
|
-
|
|
338
|
-
**Example:**
|
|
339
|
-
```typescript
|
|
340
|
-
import { formatPlainDate, today } from '@gobrand/tiempo';
|
|
341
|
-
|
|
342
|
-
const date = Temporal.PlainDate.from("2025-01-20");
|
|
343
|
-
|
|
344
|
-
formatPlainDate(date, "yyyy-MM-dd"); // "2025-01-20"
|
|
345
|
-
formatPlainDate(date, "MMMM d, yyyy"); // "January 20, 2025"
|
|
346
|
-
formatPlainDate(date, "EEEE, MMMM do, yyyy"); // "Monday, January 20th, 2025"
|
|
347
|
-
formatPlainDate(date, "MM/dd/yyyy"); // "01/20/2025"
|
|
348
|
-
|
|
349
|
-
// With locale
|
|
350
|
-
formatPlainDate(date, "MMMM d, yyyy", { locale: "es-ES" }); // "enero 20, 2025"
|
|
351
|
-
formatPlainDate(date, "EEEE", { locale: "de-DE" }); // "Montag"
|
|
352
|
-
|
|
353
|
-
// Use with today()
|
|
354
|
-
const todayFormatted = formatPlainDate(today(), "EEEE, MMMM do");
|
|
355
|
-
// "Thursday, January 23rd"
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
#### `simpleFormat(input, options?)`
|
|
359
|
-
|
|
360
|
-
Format a Temporal date in a human-friendly way: "Dec 23" or "Dec 23, 2020". By default, shows the year only if the date is not in the current year. Optionally includes time in 12-hour or 24-hour format.
|
|
361
|
-
|
|
362
|
-
**Parameters:**
|
|
363
|
-
- `input` (Temporal.PlainDate | Temporal.ZonedDateTime | Temporal.Instant): The date to format
|
|
364
|
-
- `options` (object, optional): Formatting options
|
|
365
|
-
- `locale` (string): BCP 47 language tag (default: "en-US")
|
|
366
|
-
- `year` ('auto' | 'always' | 'never'): Control year display (default: 'auto')
|
|
367
|
-
- `time` ('12h' | '24h'): Include time in output (not available for PlainDate)
|
|
368
|
-
- `timeZone` (string): IANA timezone identifier (required for Instant, optional for ZonedDateTime)
|
|
369
|
-
|
|
370
|
-
**Returns:** `string` - Human-friendly formatted date string
|
|
371
|
-
|
|
372
|
-
**Example:**
|
|
373
|
-
```typescript
|
|
374
|
-
import { simpleFormat, today, now } from '@gobrand/tiempo';
|
|
375
|
-
|
|
376
|
-
// Assuming current year is 2026
|
|
377
|
-
const date2026 = Temporal.ZonedDateTime.from("2026-12-23T15:30:00[America/New_York]");
|
|
378
|
-
const date2020 = Temporal.ZonedDateTime.from("2020-12-23T15:30:00[America/New_York]");
|
|
379
|
-
|
|
380
|
-
// Basic usage - year shown only for past years
|
|
381
|
-
simpleFormat(date2026); // "Dec 23"
|
|
382
|
-
simpleFormat(date2020); // "Dec 23, 2020"
|
|
383
|
-
|
|
384
|
-
// With time
|
|
385
|
-
simpleFormat(date2026, { time: '12h' }); // "Dec 23, 3:30 PM"
|
|
386
|
-
simpleFormat(date2026, { time: '24h' }); // "Dec 23, 15:30"
|
|
387
|
-
|
|
388
|
-
// Control year display
|
|
389
|
-
simpleFormat(date2026, { year: 'always' }); // "Dec 23, 2026"
|
|
390
|
-
simpleFormat(date2020, { year: 'never' }); // "Dec 23"
|
|
391
|
-
|
|
392
|
-
// With Instant (timeZone required)
|
|
393
|
-
const instant = Temporal.Instant.from("2026-12-23T20:30:00Z");
|
|
394
|
-
simpleFormat(instant, { timeZone: 'America/New_York' }); // "Dec 23"
|
|
395
|
-
simpleFormat(instant, { timeZone: 'America/New_York', time: '12h' }); // "Dec 23, 3:30 PM"
|
|
396
|
-
|
|
397
|
-
// With PlainDate (no time option available)
|
|
398
|
-
const plain = Temporal.PlainDate.from("2020-12-23");
|
|
399
|
-
simpleFormat(plain); // "Dec 23, 2020"
|
|
400
|
-
|
|
401
|
-
// Different locales
|
|
402
|
-
simpleFormat(date2020, { locale: 'es-ES' }); // "23 dic 2020"
|
|
403
|
-
simpleFormat(date2020, { locale: 'de-DE' }); // "23. Dez. 2020"
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Start/End Utilities
|
|
407
|
-
|
|
408
|
-
#### `today(timezone?)`
|
|
409
|
-
|
|
410
|
-
Get today's date in the system's local timezone or a specified timezone.
|
|
411
|
-
|
|
412
|
-
**Parameters:**
|
|
413
|
-
- `timezone` (string, optional): An IANA timezone identifier (e.g., `"America/New_York"`, `"Europe/Madrid"`) or `"UTC"`. If not provided, uses the system's local timezone.
|
|
414
|
-
|
|
415
|
-
**Returns:** `Temporal.PlainDate` - A Temporal.PlainDate representing today's date
|
|
416
|
-
|
|
417
|
-
**Example:**
|
|
418
|
-
```typescript
|
|
419
|
-
import { today } from '@gobrand/tiempo';
|
|
420
|
-
|
|
421
|
-
const date = today(); // 2025-01-20
|
|
422
|
-
date.year; // 2025
|
|
423
|
-
date.month; // 1
|
|
424
|
-
date.day; // 20
|
|
425
|
-
|
|
426
|
-
// Get today in a specific timezone
|
|
427
|
-
const dateInTokyo = today("Asia/Tokyo"); // 2025-01-21 (next day due to timezone)
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
#### `now(timezone?)`
|
|
431
|
-
|
|
432
|
-
Get the current date and time in the system's local timezone or a specified timezone.
|
|
433
|
-
|
|
434
|
-
**Parameters:**
|
|
435
|
-
- `timezone` (string, optional): An IANA timezone identifier (e.g., `"America/New_York"`, `"Europe/Madrid"`) or `"UTC"`. If not provided, uses the system's local timezone.
|
|
436
|
-
|
|
437
|
-
**Returns:** `Temporal.ZonedDateTime` - A Temporal.ZonedDateTime representing the current date and time
|
|
438
|
-
|
|
439
|
-
**Example:**
|
|
440
|
-
```typescript
|
|
441
|
-
import { now } from '@gobrand/tiempo';
|
|
442
|
-
|
|
443
|
-
const current = now(); // 2025-01-20T15:30:45.123-05:00[America/New_York]
|
|
444
|
-
current.hour; // 15
|
|
445
|
-
current.minute; // 30
|
|
446
|
-
|
|
447
|
-
// Get current time in a specific timezone
|
|
448
|
-
const currentInTokyo = now("Asia/Tokyo"); // 2025-01-21T05:30:45.123+09:00[Asia/Tokyo]
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
#### `startOfDay(input)` / `endOfDay(input)`
|
|
452
|
-
|
|
453
|
-
Get the start or end of the day for a given datetime.
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
import { startOfDay, endOfDay } from '@gobrand/tiempo';
|
|
457
|
-
|
|
458
|
-
const zoned = Temporal.ZonedDateTime.from('2025-01-20T15:30:00-05:00[America/New_York]');
|
|
459
|
-
|
|
460
|
-
startOfDay(zoned); // 2025-01-20T00:00:00-05:00[America/New_York]
|
|
461
|
-
endOfDay(zoned); // 2025-01-20T23:59:59.999999999-05:00[America/New_York]
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### `startOfWeek(input)` / `endOfWeek(input)`
|
|
465
|
-
|
|
466
|
-
Get the start or end of the week. Uses ISO 8601 week definition (Monday to Sunday).
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
import { startOfWeek, endOfWeek } from '@gobrand/tiempo';
|
|
470
|
-
|
|
471
|
-
const zoned = Temporal.ZonedDateTime.from('2025-01-20T15:30:00-05:00[America/New_York]'); // Monday
|
|
472
|
-
|
|
473
|
-
startOfWeek(zoned); // 2025-01-20T00:00:00-05:00[America/New_York] (Monday)
|
|
474
|
-
endOfWeek(zoned); // 2025-01-26T23:59:59.999999999-05:00[America/New_York] (Sunday)
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
#### `startOfMonth(input)` / `endOfMonth(input)`
|
|
478
|
-
|
|
479
|
-
Get the start or end of the month.
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
import { startOfMonth, endOfMonth } from '@gobrand/tiempo';
|
|
483
|
-
|
|
484
|
-
const zoned = Temporal.ZonedDateTime.from('2025-01-20T15:30:00-05:00[America/New_York]');
|
|
485
|
-
|
|
486
|
-
startOfMonth(zoned); // 2025-01-01T00:00:00-05:00[America/New_York]
|
|
487
|
-
endOfMonth(zoned); // 2025-01-31T23:59:59.999999999-05:00[America/New_York]
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
#### `startOfYear(input)` / `endOfYear(input)`
|
|
491
|
-
|
|
492
|
-
Get the start or end of the year.
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
import { startOfYear, endOfYear } from '@gobrand/tiempo';
|
|
496
|
-
|
|
497
|
-
const zoned = Temporal.ZonedDateTime.from('2025-01-20T15:30:00-05:00[America/New_York]');
|
|
498
|
-
|
|
499
|
-
startOfYear(zoned); // 2025-01-01T00:00:00-05:00[America/New_York]
|
|
500
|
-
endOfYear(zoned); // 2025-12-31T23:59:59.999999999-05:00[America/New_York]
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
### Addition/Subtraction Utilities
|
|
504
|
-
|
|
505
|
-
tiempo provides comprehensive datetime arithmetic functions for all time units, from nanoseconds to years. All functions:
|
|
506
|
-
- Accept `Temporal.Instant` or `Temporal.ZonedDateTime` as input
|
|
507
|
-
- Return `Temporal.ZonedDateTime` preserving the original timezone
|
|
508
|
-
- Properly handle DST transitions, leap years, and month-end edge cases
|
|
509
|
-
- Support negative values for subtraction
|
|
510
|
-
|
|
511
|
-
#### Addition Functions
|
|
512
|
-
|
|
513
|
-
##### `addYears(input, years)` / `addMonths(input, months)` / `addWeeks(input, weeks)` / `addDays(input, days)`
|
|
514
|
-
|
|
515
|
-
Add calendar units (years, months, weeks, or days) to a datetime.
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
import { addYears, addMonths, addWeeks, addDays } from '@gobrand/tiempo';
|
|
519
|
-
|
|
520
|
-
const date = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
521
|
-
|
|
522
|
-
addYears(date, 2); // 2027-01-20T12:00:00Z[UTC] (2 years later)
|
|
523
|
-
addMonths(date, 3); // 2025-04-20T12:00:00Z[UTC] (3 months later)
|
|
524
|
-
addWeeks(date, 2); // 2025-02-03T12:00:00Z[UTC] (2 weeks later)
|
|
525
|
-
addDays(date, 5); // 2025-01-25T12:00:00Z[UTC] (5 days later)
|
|
526
|
-
|
|
527
|
-
// Handle month-end edge cases automatically
|
|
528
|
-
const endOfJan = Temporal.Instant.from('2025-01-31T12:00:00Z');
|
|
529
|
-
addMonths(endOfJan, 1); // 2025-02-28T12:00:00Z[UTC] (Jan 31 → Feb 28)
|
|
530
|
-
|
|
531
|
-
// Negative values subtract
|
|
532
|
-
addMonths(date, -3); // 2024-10-20T12:00:00Z[UTC] (3 months earlier)
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
##### `addHours(input, hours)` / `addMinutes(input, minutes)` / `addSeconds(input, seconds)`
|
|
536
|
-
|
|
537
|
-
Add time units (hours, minutes, or seconds) to a datetime.
|
|
11
|
+
👉 **[Documentation](https://eng.gobrand.app/tiempo)**
|
|
538
12
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
addHours(time, 3); // 2025-01-20T15:00:00Z[UTC] (3 hours later)
|
|
545
|
-
addMinutes(time, 30); // 2025-01-20T12:30:00Z[UTC] (30 minutes later)
|
|
546
|
-
addSeconds(time, 45); // 2025-01-20T12:00:45Z[UTC] (45 seconds later)
|
|
547
|
-
|
|
548
|
-
// Works with ZonedDateTime and preserves timezone
|
|
549
|
-
const ny = Temporal.ZonedDateTime.from('2025-01-20T15:00:00-05:00[America/New_York]');
|
|
550
|
-
addHours(ny, 2); // 2025-01-20T17:00:00-05:00[America/New_York]
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
##### `addMilliseconds(input, ms)` / `addMicroseconds(input, μs)` / `addNanoseconds(input, ns)`
|
|
554
|
-
|
|
555
|
-
Add sub-second precision units (milliseconds, microseconds, or nanoseconds) to a datetime.
|
|
556
|
-
|
|
557
|
-
```typescript
|
|
558
|
-
import { addMilliseconds, addMicroseconds, addNanoseconds } from '@gobrand/tiempo';
|
|
559
|
-
|
|
560
|
-
const precise = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
561
|
-
|
|
562
|
-
addMilliseconds(precise, 500); // 2025-01-20T12:00:00.500Z[UTC]
|
|
563
|
-
addMicroseconds(precise, 500); // 2025-01-20T12:00:00.000500Z[UTC]
|
|
564
|
-
addNanoseconds(precise, 500); // 2025-01-20T12:00:00.000000500Z[UTC]
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
#### Subtraction Functions
|
|
568
|
-
|
|
569
|
-
All subtraction functions are convenience wrappers that call their corresponding addition functions with negated values.
|
|
570
|
-
|
|
571
|
-
##### `subYears(input, years)` / `subMonths(input, months)` / `subWeeks(input, weeks)` / `subDays(input, days)`
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
import { subYears, subMonths, subWeeks, subDays } from '@gobrand/tiempo';
|
|
575
|
-
|
|
576
|
-
const date = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
577
|
-
|
|
578
|
-
subYears(date, 2); // 2023-01-20T12:00:00Z[UTC] (2 years earlier)
|
|
579
|
-
subMonths(date, 3); // 2024-10-20T12:00:00Z[UTC] (3 months earlier)
|
|
580
|
-
subWeeks(date, 2); // 2025-01-06T12:00:00Z[UTC] (2 weeks earlier)
|
|
581
|
-
subDays(date, 5); // 2025-01-15T12:00:00Z[UTC] (5 days earlier)
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
##### `subHours(input, hours)` / `subMinutes(input, minutes)` / `subSeconds(input, seconds)`
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
import { subHours, subMinutes, subSeconds } from '@gobrand/tiempo';
|
|
588
|
-
|
|
589
|
-
const time = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
590
|
-
|
|
591
|
-
subHours(time, 3); // 2025-01-20T09:00:00Z[UTC] (3 hours earlier)
|
|
592
|
-
subMinutes(time, 30); // 2025-01-20T11:30:00Z[UTC] (30 minutes earlier)
|
|
593
|
-
subSeconds(time, 45); // 2025-01-20T11:59:15Z[UTC] (45 seconds earlier)
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
##### `subMilliseconds(input, ms)` / `subMicroseconds(input, μs)` / `subNanoseconds(input, ns)`
|
|
597
|
-
|
|
598
|
-
```typescript
|
|
599
|
-
import { subMilliseconds, subMicroseconds, subNanoseconds } from '@gobrand/tiempo';
|
|
600
|
-
|
|
601
|
-
const precise = Temporal.Instant.from('2025-01-20T12:00:00.500Z');
|
|
602
|
-
|
|
603
|
-
subMilliseconds(precise, 250); // 2025-01-20T12:00:00.250Z[UTC]
|
|
604
|
-
subMicroseconds(precise, 250); // 2025-01-20T12:00:00.499750Z[UTC]
|
|
605
|
-
subNanoseconds(precise, 250); // 2025-01-20T12:00:00.499999750Z[UTC]
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
#### Real-world Example: Meeting Scheduler
|
|
609
|
-
|
|
610
|
-
```typescript
|
|
611
|
-
import {
|
|
612
|
-
toZonedTime,
|
|
613
|
-
addDays,
|
|
614
|
-
addMinutes,
|
|
615
|
-
format
|
|
616
|
-
} from '@gobrand/tiempo';
|
|
617
|
-
|
|
618
|
-
// User's current meeting time
|
|
619
|
-
const meeting = toZonedTime('2025-01-20T15:00:00Z', 'America/New_York');
|
|
620
|
-
// 2025-01-20T10:00:00-05:00[America/New_York] (10 AM in NY)
|
|
621
|
-
|
|
622
|
-
// Reschedule to tomorrow, same time
|
|
623
|
-
const tomorrow = addDays(meeting, 1);
|
|
624
|
-
format(tomorrow, 'EEEE, MMMM do'); // "Tuesday, January 21st"
|
|
625
|
-
|
|
626
|
-
// Add 30-minute buffer before the meeting
|
|
627
|
-
const arriveBy = subMinutes(tomorrow, 30);
|
|
628
|
-
format(arriveBy, 'h:mm a'); // "9:30 AM"
|
|
629
|
-
|
|
630
|
-
// Schedule follow-up 2 weeks later
|
|
631
|
-
const followUp = addWeeks(tomorrow, 2);
|
|
632
|
-
format(followUp, 'MMM d'); // "Feb 4"
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
### Comparison Utilities
|
|
636
|
-
|
|
637
|
-
#### `isBefore(date1, date2)` / `isAfter(date1, date2)`
|
|
638
|
-
|
|
639
|
-
Check if the first datetime is before or after the second. Compares the underlying instant in time.
|
|
640
|
-
|
|
641
|
-
```typescript
|
|
642
|
-
import { isBefore, isAfter } from '@gobrand/tiempo';
|
|
643
|
-
|
|
644
|
-
const earlier = Temporal.ZonedDateTime.from('2025-01-20T10:00:00-05:00[America/New_York]');
|
|
645
|
-
const later = Temporal.ZonedDateTime.from('2025-01-20T15:00:00-05:00[America/New_York]');
|
|
646
|
-
|
|
647
|
-
isBefore(earlier, later); // true
|
|
648
|
-
isBefore(later, earlier); // false
|
|
649
|
-
|
|
650
|
-
isAfter(later, earlier); // true
|
|
651
|
-
isAfter(earlier, later); // false
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Difference Utilities
|
|
655
|
-
|
|
656
|
-
All difference functions compare the underlying instant in time and return a positive value if laterDate is after earlierDate, negative if before.
|
|
657
|
-
|
|
658
|
-
#### Time-based precision (instant comparison)
|
|
659
|
-
|
|
660
|
-
##### `differenceInNanoseconds(laterDate, earlierDate)`
|
|
661
|
-
|
|
662
|
-
Returns the difference in nanoseconds as a BigInt. Provides the highest precision available in Temporal.
|
|
663
|
-
|
|
664
|
-
```typescript
|
|
665
|
-
import { differenceInNanoseconds } from '@gobrand/tiempo';
|
|
666
|
-
|
|
667
|
-
const later = Temporal.Instant.from('2025-01-20T12:30:20.000000500Z');
|
|
668
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:30:20.000000000Z');
|
|
669
|
-
differenceInNanoseconds(later, earlier); // 500n
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
##### `differenceInMicroseconds(laterDate, earlierDate)`
|
|
673
|
-
|
|
674
|
-
Returns the difference in microseconds (1/1,000,000 second).
|
|
675
|
-
|
|
676
|
-
```typescript
|
|
677
|
-
import { differenceInMicroseconds } from '@gobrand/tiempo';
|
|
678
|
-
|
|
679
|
-
const later = Temporal.Instant.from('2025-01-20T12:30:20.001000Z');
|
|
680
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:30:20.000000Z');
|
|
681
|
-
differenceInMicroseconds(later, earlier); // 1000
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
##### `differenceInMilliseconds(laterDate, earlierDate)`
|
|
685
|
-
|
|
686
|
-
Returns the difference in milliseconds (1/1,000 second).
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
import { differenceInMilliseconds } from '@gobrand/tiempo';
|
|
690
|
-
|
|
691
|
-
const later = Temporal.Instant.from('2025-01-20T12:30:21.700Z');
|
|
692
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:30:20.600Z');
|
|
693
|
-
differenceInMilliseconds(later, earlier); // 1100
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
##### `differenceInSeconds(laterDate, earlierDate)`
|
|
697
|
-
|
|
698
|
-
Returns the difference in seconds (truncates sub-second precision).
|
|
699
|
-
|
|
700
|
-
```typescript
|
|
701
|
-
import { differenceInSeconds } from '@gobrand/tiempo';
|
|
702
|
-
|
|
703
|
-
const later = Temporal.Instant.from('2025-01-20T12:30:25Z');
|
|
704
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:30:20Z');
|
|
705
|
-
differenceInSeconds(later, earlier); // 5
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
##### `differenceInMinutes(laterDate, earlierDate)`
|
|
709
|
-
|
|
710
|
-
Returns the difference in minutes (truncates sub-minute precision).
|
|
711
|
-
|
|
712
|
-
```typescript
|
|
713
|
-
import { differenceInMinutes } from '@gobrand/tiempo';
|
|
714
|
-
|
|
715
|
-
const later = Temporal.Instant.from('2025-01-20T12:45:00Z');
|
|
716
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:30:00Z');
|
|
717
|
-
differenceInMinutes(later, earlier); // 15
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
##### `differenceInHours(laterDate, earlierDate)`
|
|
721
|
-
|
|
722
|
-
Returns the difference in hours (truncates sub-hour precision).
|
|
723
|
-
|
|
724
|
-
```typescript
|
|
725
|
-
import { differenceInHours } from '@gobrand/tiempo';
|
|
726
|
-
|
|
727
|
-
const later = Temporal.Instant.from('2025-01-20T18:00:00Z');
|
|
728
|
-
const earlier = Temporal.Instant.from('2025-01-20T15:00:00Z');
|
|
729
|
-
differenceInHours(later, earlier); // 3
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
#### Calendar-aware precision
|
|
733
|
-
|
|
734
|
-
These functions use Temporal's `until()` method to account for variable-length calendar units.
|
|
735
|
-
|
|
736
|
-
##### `differenceInDays(laterDate, earlierDate)`
|
|
737
|
-
|
|
738
|
-
Returns the difference in days. Calendar-aware, so it properly handles DST transitions where days can be 23, 24, or 25 hours.
|
|
739
|
-
|
|
740
|
-
```typescript
|
|
741
|
-
import { differenceInDays } from '@gobrand/tiempo';
|
|
742
|
-
|
|
743
|
-
const later = Temporal.Instant.from('2025-01-25T12:00:00Z');
|
|
744
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
745
|
-
differenceInDays(later, earlier); // 5
|
|
746
|
-
|
|
747
|
-
// Handles DST transitions correctly
|
|
748
|
-
const afterDst = Temporal.ZonedDateTime.from('2025-03-10T12:00:00-04:00[America/New_York]');
|
|
749
|
-
const beforeDst = Temporal.ZonedDateTime.from('2025-03-08T12:00:00-05:00[America/New_York]');
|
|
750
|
-
differenceInDays(afterDst, beforeDst); // 2 (calendar days, not 48 hours)
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
##### `differenceInWeeks(laterDate, earlierDate)`
|
|
754
|
-
|
|
755
|
-
Returns the difference in weeks (7-day periods).
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
import { differenceInWeeks } from '@gobrand/tiempo';
|
|
759
|
-
|
|
760
|
-
const later = Temporal.Instant.from('2025-02-10T12:00:00Z');
|
|
761
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
762
|
-
differenceInWeeks(later, earlier); // 3
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
##### `differenceInMonths(laterDate, earlierDate)`
|
|
766
|
-
|
|
767
|
-
Returns the difference in months. Calendar-aware, properly handling months with different numbers of days (28-31).
|
|
768
|
-
|
|
769
|
-
```typescript
|
|
770
|
-
import { differenceInMonths } from '@gobrand/tiempo';
|
|
771
|
-
|
|
772
|
-
const later = Temporal.Instant.from('2025-04-20T12:00:00Z');
|
|
773
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
774
|
-
differenceInMonths(later, earlier); // 3
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
##### `differenceInYears(laterDate, earlierDate)`
|
|
778
|
-
|
|
779
|
-
Returns the difference in years. Calendar-aware, properly handling leap years (366 days) and regular years (365 days).
|
|
780
|
-
|
|
781
|
-
```typescript
|
|
782
|
-
import { differenceInYears } from '@gobrand/tiempo';
|
|
783
|
-
|
|
784
|
-
const later = Temporal.Instant.from('2028-01-20T12:00:00Z');
|
|
785
|
-
const earlier = Temporal.Instant.from('2025-01-20T12:00:00Z');
|
|
786
|
-
differenceInYears(later, earlier); // 3
|
|
787
|
-
|
|
788
|
-
// Calculate age
|
|
789
|
-
const today = Temporal.ZonedDateTime.from('2025-01-20T12:00:00Z[UTC]');
|
|
790
|
-
const birthdate = Temporal.ZonedDateTime.from('1990-01-20T12:00:00Z[UTC]');
|
|
791
|
-
differenceInYears(today, birthdate); // 35
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
#### `isFuture(date)` / `isPast(date)`
|
|
795
|
-
|
|
796
|
-
Check if a datetime is in the future or past relative to the current moment. Compares against `Temporal.Now.instant()`.
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
import { isFuture, isPast } from '@gobrand/tiempo';
|
|
800
|
-
|
|
801
|
-
const tomorrow = Temporal.Now.zonedDateTimeISO().add({ days: 1 });
|
|
802
|
-
const yesterday = Temporal.Now.zonedDateTimeISO().subtract({ days: 1 });
|
|
803
|
-
|
|
804
|
-
isFuture(tomorrow); // true
|
|
805
|
-
isFuture(yesterday); // false
|
|
806
|
-
|
|
807
|
-
isPast(yesterday); // true
|
|
808
|
-
isPast(tomorrow); // false
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
#### `isSameDay(date1, date2)`
|
|
812
|
-
|
|
813
|
-
Check if two datetimes fall on the same calendar day. Compares year, month, and day fields directly.
|
|
814
|
-
|
|
815
|
-
**Important:** For ZonedDateTime inputs with different timezones, each date is compared in its own timezone. Convert to a common timezone first if you need a specific perspective.
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
import { isSameDay } from '@gobrand/tiempo';
|
|
819
|
-
|
|
820
|
-
// Same timezone, same day
|
|
821
|
-
const morning = Temporal.ZonedDateTime.from('2025-01-20T08:00:00Z[UTC]');
|
|
822
|
-
const evening = Temporal.ZonedDateTime.from('2025-01-20T23:00:00Z[UTC]');
|
|
823
|
-
isSameDay(morning, evening); // true
|
|
824
|
-
|
|
825
|
-
// Different timezones - compares in their respective timezones
|
|
826
|
-
const ny = Temporal.ZonedDateTime.from('2025-01-20T23:00:00-05:00[America/New_York]');
|
|
827
|
-
const tokyo = Temporal.ZonedDateTime.from('2025-01-21T13:00:00+09:00[Asia/Tokyo]');
|
|
828
|
-
isSameDay(ny, tokyo); // false (Jan 20 in NY, Jan 21 in Tokyo)
|
|
829
|
-
|
|
830
|
-
// Convert to UTC to compare in UTC timezone
|
|
831
|
-
isSameDay(ny.withTimeZone('UTC'), tokyo.withTimeZone('UTC')); // true (both Jan 21 in UTC)
|
|
832
|
-
|
|
833
|
-
// Instant inputs are automatically converted to UTC
|
|
834
|
-
const instant1 = Temporal.Instant.from('2025-01-20T10:00:00Z');
|
|
835
|
-
const instant2 = Temporal.Instant.from('2025-01-20T23:00:00Z');
|
|
836
|
-
isSameDay(instant1, instant2); // true (both Jan 20 in UTC)
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
## Drizzle ORM Integration
|
|
840
|
-
|
|
841
|
-
tiempo seamlessly integrates with Drizzle ORM for database datetime operations. When using Drizzle with PostgreSQL `timestamptz` columns and `mode: 'date'`, tiempo provides utilities to convert between Date objects and Temporal.
|
|
842
|
-
|
|
843
|
-
```typescript
|
|
844
|
-
import { toZonedTime, toUtc, toDate } from '@gobrand/tiempo';
|
|
845
|
-
|
|
846
|
-
// 1. Reading from database (Drizzle returns Date with mode: 'date')
|
|
847
|
-
const post = await db.query.posts.findFirst();
|
|
848
|
-
const publishedAt = post.publishedAt; // Date object
|
|
849
|
-
|
|
850
|
-
// 2. Convert to user's timezone for display
|
|
851
|
-
const userTimezone = "America/New_York";
|
|
852
|
-
const zonedTime = toZonedTime(publishedAt, userTimezone);
|
|
853
|
-
console.log(zonedTime.hour); // Local hour in user's timezone
|
|
854
|
-
|
|
855
|
-
// 3. User reschedules post to tomorrow at 3 PM their time
|
|
856
|
-
const rescheduled = zonedTime.add({ days: 1 }).with({ hour: 15, minute: 0 });
|
|
857
|
-
|
|
858
|
-
// 4. Convert back to Date for database storage
|
|
859
|
-
const dateForDb = toDate(rescheduled);
|
|
860
|
-
await db.update(posts).set({ publishedAt: dateForDb }).where(eq(posts.id, post.id));
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
**Why this works:**
|
|
864
|
-
- Drizzle's `mode: 'date'` returns JavaScript `Date` objects (timestamps)
|
|
865
|
-
- `toZonedTime(date, tz)` converts the Date to a timezone-aware Temporal object
|
|
866
|
-
- Work with Temporal's powerful API for date arithmetic and manipulation
|
|
867
|
-
- `toDate(temporal)` converts back to Date for Drizzle storage
|
|
868
|
-
|
|
869
|
-
## Real World Examples
|
|
870
|
-
|
|
871
|
-
### Example 1: Social Media Post Scheduler
|
|
872
|
-
|
|
873
|
-
This example demonstrates the complete workflow of working with datetimes in a frontend application: receiving a UTC ISO 8601 string from your backend, converting it to a Temporal object in the user's timezone, formatting it for display, manipulating it with tiempo's arithmetic functions, and sending it back to your backend as a UTC ISO 8601 string.
|
|
13
|
+
- **Zero timezone bugs** - Real IANA timezone support, not UTC offset hacks
|
|
14
|
+
- **DST-aware math** - `addDays(1)` means tomorrow, even across clock changes
|
|
15
|
+
- **Nanosecond precision** - When milliseconds aren't enough
|
|
16
|
+
- **Type-safe** - Full TypeScript, catches datetime errors at compile time
|
|
17
|
+
- **Familiar API** - If you've used date-fns, you already know this
|
|
874
18
|
|
|
875
19
|
```typescript
|
|
876
|
-
import {
|
|
877
|
-
toZonedTime,
|
|
878
|
-
toUtcString,
|
|
879
|
-
format,
|
|
880
|
-
addDays,
|
|
881
|
-
addHours,
|
|
882
|
-
subMinutes
|
|
883
|
-
} from '@gobrand/tiempo';
|
|
884
|
-
|
|
885
|
-
// 1. Receive UTC ISO 8601 string from backend
|
|
886
|
-
const scheduledAtUTC = "2025-01-20T20:00:00.000Z";
|
|
887
|
-
|
|
888
|
-
// 2. Convert to user's timezone (Temporal.ZonedDateTime)
|
|
889
|
-
const userTimezone = "America/New_York";
|
|
890
|
-
const zonedDateTime = toZonedTime(scheduledAtUTC, userTimezone);
|
|
891
|
-
|
|
892
|
-
// 3. Format for display in the UI
|
|
893
|
-
const displayTime = format(zonedDateTime, "EEEE, MMMM do 'at' h:mm a");
|
|
894
|
-
console.log(displayTime); // "Monday, January 20th at 3:00 PM"
|
|
895
|
-
|
|
896
|
-
// 4. User wants to reschedule to tomorrow, 2 hours later
|
|
897
|
-
const tomorrow = addDays(zonedDateTime, 1);
|
|
898
|
-
const twoHoursLater = addHours(tomorrow, 2);
|
|
899
|
-
|
|
900
|
-
// 5. Format the updated time for confirmation
|
|
901
|
-
const confirmTime = format(twoHoursLater, "EEEE 'at' h:mm a");
|
|
902
|
-
console.log(`Rescheduled to ${confirmTime}`); // "Tuesday at 5:00 PM"
|
|
903
|
-
|
|
904
|
-
// 6. Convert back to UTC ISO 8601 string for backend
|
|
905
|
-
const updatedUTC = toUtcString(twoHoursLater);
|
|
906
|
-
console.log(updatedUTC); // "2025-01-21T22:00:00Z"
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
### Example 2: Meeting Reminder System
|
|
910
|
-
|
|
911
|
-
```typescript
|
|
912
|
-
import {
|
|
913
|
-
toZonedTime,
|
|
914
|
-
format,
|
|
915
|
-
subMinutes,
|
|
916
|
-
subHours,
|
|
917
|
-
differenceInMinutes,
|
|
918
|
-
isFuture
|
|
919
|
-
} from '@gobrand/tiempo';
|
|
920
|
-
|
|
921
|
-
// Meeting scheduled for 2 PM
|
|
922
|
-
const meeting = toZonedTime('2025-01-20T19:00:00Z', 'America/New_York');
|
|
923
|
-
// 2025-01-20T14:00:00-05:00[America/New_York]
|
|
20
|
+
import { toZonedTime, addDays, format, toUtcString } from '@gobrand/tiempo';
|
|
924
21
|
|
|
925
|
-
//
|
|
926
|
-
const
|
|
927
|
-
{ time: subMinutes(meeting, 15), label: '15 minutes before' },
|
|
928
|
-
{ time: subHours(meeting, 1), label: '1 hour before' },
|
|
929
|
-
{ time: subHours(meeting, 24), label: '1 day before' },
|
|
930
|
-
];
|
|
22
|
+
// Backend sends UTC
|
|
23
|
+
const utc = "2025-03-09T07:00:00Z";
|
|
931
24
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
});
|
|
25
|
+
// Convert to user's timezone, manipulate, format
|
|
26
|
+
const userTime = toZonedTime(utc, "America/New_York");
|
|
27
|
+
const tomorrow = addDays(userTime, 1); // DST transition handled correctly
|
|
28
|
+
const display = format(tomorrow, "EEEE 'at' h:mm a"); // "Monday at 2:00 AM"
|
|
938
29
|
|
|
939
|
-
//
|
|
940
|
-
const
|
|
941
|
-
const minutesUntil = differenceInMinutes(meeting, now);
|
|
942
|
-
console.log(`Meeting starts in ${minutesUntil} minutes`);
|
|
30
|
+
// Send back to backend as UTC
|
|
31
|
+
const payload = toUtcString(tomorrow); // "2025-03-10T06:00:00Z"
|
|
943
32
|
```
|
|
944
33
|
|
|
945
|
-
|
|
34
|
+
## Install
|
|
946
35
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
#### `isPlainDateBefore(date1, date2)` / `isPlainDateAfter(date1, date2)` / `isPlainDateEqual(date1, date2)`
|
|
950
|
-
|
|
951
|
-
Compare two `Temporal.PlainDate` values. Unlike `isBefore`/`isAfter` which compare instants in time, these functions compare pure calendar dates.
|
|
952
|
-
|
|
953
|
-
```typescript
|
|
954
|
-
import { isPlainDateBefore, isPlainDateAfter, isPlainDateEqual } from '@gobrand/tiempo';
|
|
955
|
-
|
|
956
|
-
const jan20 = Temporal.PlainDate.from('2025-01-20');
|
|
957
|
-
const jan25 = Temporal.PlainDate.from('2025-01-25');
|
|
958
|
-
|
|
959
|
-
isPlainDateBefore(jan20, jan25); // true
|
|
960
|
-
isPlainDateBefore(jan25, jan20); // false
|
|
961
|
-
|
|
962
|
-
isPlainDateAfter(jan25, jan20); // true
|
|
963
|
-
isPlainDateAfter(jan20, jan25); // false
|
|
964
|
-
|
|
965
|
-
isPlainDateEqual(jan20, Temporal.PlainDate.from('2025-01-20')); // true
|
|
966
|
-
isPlainDateEqual(jan20, jan25); // false
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
**When to use PlainDate vs Instant/ZonedDateTime:**
|
|
970
|
-
- Use `isPlainDateBefore`/`isPlainDateAfter` when comparing calendar dates (e.g., "Is January 20th before January 25th?")
|
|
971
|
-
- Use `isBefore`/`isAfter` when comparing specific moments in time (e.g., "Did event A happen before event B?")
|
|
972
|
-
|
|
973
|
-
```typescript
|
|
974
|
-
// Calendar UI: Disable dates before today
|
|
975
|
-
const isDateDisabled = (date: Temporal.PlainDate) =>
|
|
976
|
-
isPlainDateBefore(date, today());
|
|
977
|
-
|
|
978
|
-
// Booking system: Check if selected date is in the past
|
|
979
|
-
const isPastDate = (date: Temporal.PlainDate) =>
|
|
980
|
-
isPlainDateBefore(date, today());
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
## Browser Support
|
|
984
|
-
|
|
985
|
-
The Temporal API is a Stage 3 TC39 proposal. The polyfill `@js-temporal/polyfill` is included as a dependency, so you're ready to go!
|
|
986
|
-
|
|
987
|
-
```typescript
|
|
988
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
36
|
+
```bash
|
|
37
|
+
npm install @gobrand/tiempo
|
|
989
38
|
```
|
|
990
39
|
|
|
991
|
-
##
|
|
40
|
+
## Docs
|
|
992
41
|
|
|
993
|
-
|
|
42
|
+
**[eng.gobrand.app/tiempo](https://eng.gobrand.app/tiempo)** — Full API reference, examples, and guides.
|
|
994
43
|
|
|
995
44
|
## License
|
|
996
45
|
|
|
997
|
-
MIT © [
|
|
998
|
-
|
|
999
|
-
## Built by Go Brand
|
|
1000
|
-
|
|
1001
|
-
tiempo is built and maintained by [Go Brand](https://gobrand.app) - a modern social media management platform.
|
|
46
|
+
MIT © [Ruben Costa](https://x.com/PonziChad) / [Go Brand](https://gobrand.app)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobrand/tiempo",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
4
4
|
"description": "Lightweight utility functions for converting between UTC and timezone-aware datetimes using the Temporal API",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"bugs": {
|
|
29
29
|
"url": "https://github.com/go-brand/tiempo/issues"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://
|
|
31
|
+
"homepage": "https://eng.gobrand.app/tiempo",
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsup && tsc --emitDeclarationOnly",
|
|
34
34
|
"clean": "git clean -xdf .cache .turbo dist node_modules tsconfig.tsbuildinfo",
|