{"_id":"@brandonwie/dayjs-util","_rev":"5-4c1f175ddae63c196741f60831ea579b","name":"@brandonwie/dayjs-util","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@brandonwie/dayjs-util","version":"0.1.0","keywords":["date","timezone","dayjs","utc","calendar","date-util"],"author":{"name":"brandonwie"},"license":"MIT","_id":"@brandonwie/dayjs-util@0.1.0","maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"homepage":"https://github.com/brandonwie/dayjs-util#readme","bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"dist":{"shasum":"2060e07fbf4423b0ce5793babca7db2a2627fb69","tarball":"https://registry.npmjs.org/@brandonwie/dayjs-util/-/dayjs-util-0.1.0.tgz","fileCount":19,"integrity":"sha512-njDw7Vvs+NcloLy5zkNChymLt2RRppIqO1Fe6gD2/41H+54NbMi9EQfB2eHqaiTqKluwVmyvNZQmYNr3Z6fjNw==","signatures":[{"sig":"MEYCIQCrXvyFhlEGu1XfiCKg2RAEZaL3wUjAE+9e0zEVE1d0swIhAMWf6xcFyj2YgWk3K2OuD+9CU5kWcpRaNoaWWOORJKEj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123214},"main":"./dist/index.cjs","type":"module","_from":"file:brandonwie-dayjs-util-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./event":{"import":{"types":"./dist/event.d.ts","default":"./dist/event.js"},"require":{"types":"./dist/event.d.cts","default":"./dist/event.cjs"}}},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brandonwie","email":"brandonwie.cs@gmail.com"},"_resolved":"/private/var/folders/t3/59glz3g57_sf_fy84_1k4bpw0000gn/T/9cea072bb8ae854b552756f188c4e07f/brandonwie-dayjs-util-0.1.0.tgz","_integrity":"sha512-njDw7Vvs+NcloLy5zkNChymLt2RRppIqO1Fe6gD2/41H+54NbMi9EQfB2eHqaiTqKluwVmyvNZQmYNr3Z6fjNw==","repository":{"url":"git+https://github.com/brandonwie/dayjs-util.git","type":"git"},"_npmVersion":"11.9.0","description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","dayjs":"^1.11.13","vitest":"^3.1.0","typescript":"^5.7.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"dayjs":"^1.11.0"},"_npmOperationalInternal":{"tmp":"tmp/dayjs-util_0.1.0_1774332323890_0.12420213187727724","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@brandonwie/dayjs-util","version":"0.2.0","keywords":["date","timezone","dayjs","utc","calendar","date-util"],"author":{"name":"brandonwie"},"license":"MIT","_id":"@brandonwie/dayjs-util@0.2.0","maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"homepage":"https://github.com/brandonwie/dayjs-util#readme","bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"dist":{"shasum":"94b816b5f39ef3e0b4b58d781f88f7f1d8f80a5d","tarball":"https://registry.npmjs.org/@brandonwie/dayjs-util/-/dayjs-util-0.2.0.tgz","fileCount":19,"integrity":"sha512-ZqMVkPQpb5Uf/AUiwM+PPjP/b8Cq9abAi2qa0Gj9PzrFcund92GnLyjHJR+hdAsRflyMoUMBfQl7Xw7bb1DSFw==","signatures":[{"sig":"MEUCIAv+Bm9BBNW4i46GY+++yZYk4UCmtzwWMvuaGXRQnoWPAiEAnurk5CB2AKBXYQyGj6I8mcdJZYrOX0XIWH+Pgz3Uk+4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brandonwie%2fdayjs-util@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":123291},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./event":{"import":{"types":"./dist/event.d.ts","default":"./dist/event.js"},"require":{"types":"./dist/event.d.cts","default":"./dist/event.cjs"}}},"gitHead":"d8792848ec5341f62dcf23ae20d2d8aee46dcd49","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:86b5be2e-0790-4e12-9c67-6bfde7af9152"}},"repository":{"url":"git+https://github.com/brandonwie/dayjs-util.git","type":"git"},"_npmVersion":"11.12.0","description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","directories":{},"_nodeVersion":"22.22.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","dayjs":"^1.11.13","vitest":"^3.1.0","typescript":"^5.7.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"dayjs":"^1.11.0"},"_npmOperationalInternal":{"tmp":"tmp/dayjs-util_0.2.0_1774334053153_0.3775131965623111","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@brandonwie/dayjs-util","version":"0.3.0","keywords":["date","timezone","dayjs","utc","calendar","date-util"],"author":{"name":"brandonwie"},"license":"MIT","_id":"@brandonwie/dayjs-util@0.3.0","maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"homepage":"https://github.com/brandonwie/dayjs-util#readme","bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"dist":{"shasum":"92d754c9bb7ba3cd2a4b0fe82b3a6e09af5c30d9","tarball":"https://registry.npmjs.org/@brandonwie/dayjs-util/-/dayjs-util-0.3.0.tgz","fileCount":20,"integrity":"sha512-GJxzidOnt7VKvrHEXK7ALZ2eCyyPT6U38mEIktvD0tGeEai4O6S5bpmMMHmweNKpHyZ6+XSy18uUsGCNrl2c1A==","signatures":[{"sig":"MEYCIQD2k9i9Afhr1BY54eZeEQ0YqTCTArarExShx7J1kZrljgIhANRVFDed4fuv5cdfWQ4x3qYeRTGfWhP29LIyTnXhHcmp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brandonwie%2fdayjs-util@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":273693},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./event":{"import":{"types":"./dist/event.d.ts","default":"./dist/event.js"},"require":{"types":"./dist/event.d.cts","default":"./dist/event.cjs"}}},"gitHead":"2169ad33320a78b5941dcf16f122261db15f09d0","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:86b5be2e-0790-4e12-9c67-6bfde7af9152"}},"repository":{"url":"git+https://github.com/brandonwie/dayjs-util.git","type":"git"},"_npmVersion":"11.12.1","description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","dayjs":"^1.11.13","vitest":"^3.1.0","typescript":"^5.7.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"dayjs":"^1.11.0"},"_npmOperationalInternal":{"tmp":"tmp/dayjs-util_0.3.0_1774982712185_0.8417113182441722","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@brandonwie/dayjs-util","version":"0.3.1","keywords":["date","timezone","dayjs","utc","calendar","date-util"],"author":{"name":"brandonwie"},"license":"MIT","_id":"@brandonwie/dayjs-util@0.3.1","maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"homepage":"https://github.com/brandonwie/dayjs-util#readme","bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"dist":{"shasum":"952ec5f57a0dc0f023f35b6a8cd72eb8829747b9","tarball":"https://registry.npmjs.org/@brandonwie/dayjs-util/-/dayjs-util-0.3.1.tgz","fileCount":20,"integrity":"sha512-/6l2xDTgoYiax6giqoDRF90bkD9sQGfUWvg/k9hqQXBkavO2OO+YtrfRYR8bVUmZwpzsOIH/4ooCQ6Wm4CEPqA==","signatures":[{"sig":"MEUCIQCtVfq93H9WuwAwy2HEP+eDFGYv4VXBacKlfLVlCwUMwAIga833xNhmp9Btz12LHz3PJl1CsK16BKgD3COkxjw5NDU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brandonwie%2fdayjs-util@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":273693},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./event":{"import":{"types":"./dist/event.d.ts","default":"./dist/event.js"},"require":{"types":"./dist/event.d.cts","default":"./dist/event.cjs"}}},"gitHead":"c1821793af97519e8cf1defcc0abcbb34f16f45b","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:86b5be2e-0790-4e12-9c67-6bfde7af9152"}},"repository":{"url":"git+https://github.com/brandonwie/dayjs-util.git","type":"git"},"_npmVersion":"11.12.1","description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","dayjs":"^1.11.13","vitest":"^3.1.0","typescript":"^5.7.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"dayjs":"^1.11.0"},"_npmOperationalInternal":{"tmp":"tmp/dayjs-util_0.3.1_1775012529214_0.7088383774995324","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@brandonwie/dayjs-util","version":"0.4.0","description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./event":{"import":{"types":"./dist/event.d.ts","default":"./dist/event.js"},"require":{"types":"./dist/event.d.cts","default":"./dist/event.cjs"}}},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"keywords":["date","timezone","dayjs","utc","calendar","date-util"],"author":{"name":"brandonwie"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/brandonwie/dayjs-util.git"},"peerDependencies":{"dayjs":"^1.11.0"},"devDependencies":{"@vitest/coverage-v8":"^3.2.4","dayjs":"^1.11.13","tsup":"^8.4.0","typescript":"^5.7.0","vitest":"^3.1.0"},"gitHead":"73abfbe0cfc68f2b035b9f8411e39fba11f86eb5","_id":"@brandonwie/dayjs-util@0.4.0","bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"homepage":"https://github.com/brandonwie/dayjs-util#readme","_nodeVersion":"24.14.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-yncrTivh4WRsGDN6l8++lxgdy1tNlzU0q1TR0LPjlEO8QMgrAjJ/v+KeeXJtBazTqViNhqMG07Ba/3kfmvnRkg==","shasum":"d90515b5ff561b386cea0ed41975106e6892d4df","tarball":"https://registry.npmjs.org/@brandonwie/dayjs-util/-/dayjs-util-0.4.0.tgz","fileCount":18,"unpackedSize":335168,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brandonwie%2fdayjs-util@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC3VXdOjlu+yRLayOO8iPsZOmSrFeve28M+Y9JtrjLZPwIgKhqGOqsvyJHtKV4rP/Tj1IIKS/HoWMDqBPvIz1Jgtz8="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:86b5be2e-0790-4e12-9c67-6bfde7af9152"}},"directories":{},"maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dayjs-util_0.4.0_1775023848748_0.45890789414259436"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-24T06:05:23.791Z","modified":"2026-04-01T06:10:49.262Z","0.1.0":"2026-03-24T06:05:24.036Z","0.2.0":"2026-03-24T06:34:13.297Z","0.3.0":"2026-03-31T18:45:12.335Z","0.3.1":"2026-04-01T03:02:09.386Z","0.4.0":"2026-04-01T06:10:48.902Z"},"bugs":{"url":"https://github.com/brandonwie/dayjs-util/issues"},"author":{"name":"brandonwie"},"license":"MIT","homepage":"https://github.com/brandonwie/dayjs-util#readme","keywords":["date","timezone","dayjs","utc","calendar","date-util"],"repository":{"type":"git","url":"git+https://github.com/brandonwie/dayjs-util.git"},"description":"Timezone-safe date utility wrapper for dayjs — built for calendar applications","maintainers":[{"name":"brandonwie","email":"brandonwie.cs@gmail.com"}],"readme":"# @brandonwie/dayjs-util\n\n[![npm version](https://img.shields.io/npm/v/@brandonwie/dayjs-util.svg)](https://www.npmjs.com/package/@brandonwie/dayjs-util)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@brandonwie/dayjs-util)](https://bundlephobia.com/package/@brandonwie/dayjs-util)\n\n**English** | [한국어](./README.ko.md)\n\nTimezone-safe date utility wrapper for [dayjs](https://day.js.org/) — built for calendar applications.\n\n## Why not just use dayjs directly?\n\n| Concern                     | Raw dayjs                                       | This library                                               |\n| --------------------------- | ----------------------------------------------- | ---------------------------------------------------------- |\n| **Timezone ambiguity**      | `dayjs(\"2025-01-01\")` — UTC? Local? Seoul?      | `DayjsUtil.tzParse(\"2025-01-01\", \"Asia/Seoul\")` — explicit |\n| **All-day vs timed events** | No built-in distinction                         | `stripTimezoneToUTC()` vs `convertToUTCDate()`             |\n| **Plugin setup**            | Must remember `extend(utc)`, `extend(timezone)` | Auto-loaded once at import                                 |\n| **Return type clarity**     | Everything returns `Dayjs`                      | `*Date` → JS Date, `*String` → string, bare → Dayjs        |\n\n## Install\n\n```bash\npnpm add @brandonwie/dayjs-util dayjs\n# or\nnpm install @brandonwie/dayjs-util dayjs\n```\n\n> `dayjs` is a **peer dependency** — you control the version.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [API Reference](#api-reference)\n  - [Parsing](#parsing)\n  - [Current Time](#current-time)\n  - [Conversion](#conversion)\n  - [Arithmetic](#arithmetic)\n  - [Boundaries](#boundaries)\n  - [Formatting](#formatting)\n  - [Timestamps](#timestamps)\n  - [Duration](#duration)\n  - [Comparison](#comparison)\n  - [Time Manipulation](#time-manipulation)\n  - [Calendar Domain](#calendar-domain)\n  - [Validation](#validation)\n- [EventDateHandler](#eventdatehandler-calendar-events)\n  - [toAllDayUTC](#toalldayutcstart-end) | [toTimedUTC](#totimedutcstart-end-timezone) | [normalize](#normalizeparams)\n- [Migration Guide](#migration-guide-new-date--dayjsutil)\n- [Design Decisions](#design-decisions)\n- [How DST is Handled](#how-dst-is-handled)\n- [Breaking Changes in v0.4.0](#breaking-changes-in-v040)\n- [References](#references)\n- [License](#license)\n\n## Quick Start\n\n```typescript\nimport { DayjsUtil } from \"@brandonwie/dayjs-util\";\n\n// Current time in any timezone\nconst now = DayjsUtil.now(\"America/New_York\");\n\n// Parse a date AS being in a specific timezone\nconst seoulMidnight = DayjsUtil.tzParse(\"2025-01-01 00:00:00\", \"Asia/Seoul\");\n// → Dayjs representing 2025-01-01 00:00:00 KST (2024-12-31 15:00:00 UTC)\n\n// Convert to UTC Date for database storage\nconst utcDate = DayjsUtil.convertToUTCDate(\"2025-06-15T09:00:00+09:00\");\n// → Date(2025-06-15T00:00:00.000Z)\n\n// All-day events: preserve time, strip timezone\nconst allDay = DayjsUtil.stripTimezoneToUTC(\"2025-06-15T00:00:00+09:00\");\n// → Date(2025-06-15T00:00:00.000Z)  ← time preserved!\n\n// Format for API responses\nDayjsUtil.formatUTCString(new Date()); // \"2025-06-15T00:00:00Z\"\nDayjsUtil.formatISOString(new Date(), \"Asia/Seoul\"); // \"2025-06-15T09:00:00+09:00\"\n```\n\n## API Reference\n\n### Parsing\n\n| Method                       | Returns | Description                                           |\n| ---------------------------- | ------- | ----------------------------------------------------- |\n| `utc(date?)`                 | `Dayjs` | Create/convert to UTC                                 |\n| `tz(date?, timezone?)`       | `Dayjs` | Convert TO timezone (same instant, different display) |\n| `tzParse(str, timezone?)`    | `Dayjs` | Parse AS timezone (different instant!)                |\n| `parseToTz(str?, timezone?)` | `Dayjs` | Parse string, display in timezone                     |\n\n#### `tz()` vs `tzParse()` — the critical difference\n\n```typescript\nconst str = \"2025-01-01 00:00:00\";\n\n// tz(): parses in server timezone, converts display to Seoul\nDayjsUtil.tz(str, \"Asia/Seoul\").toDate();\n// → 2025-01-01T00:00:00.000Z (if server is UTC)\n\n// tzParse(): interprets the string AS Seoul time\nDayjsUtil.tzParse(str, \"Asia/Seoul\").toDate();\n// → 2024-12-31T15:00:00.000Z (9 hours earlier!)\n```\n\nUse `tzParse()` when processing user input in their timezone. Use `tz()` when converting a known UTC instant for display.\n\n```typescript\n// utc(): create a Dayjs object in UTC\nDayjsUtil.utc(\"2025-06-15T09:00:00+09:00\");\n// → Dayjs representing 2025-06-15T00:00:00Z\n\nDayjsUtil.utc(); // current time in UTC\n\n// parseToTz(): parse a string and display in timezone\nDayjsUtil.parseToTz(\"2025-06-15T00:00:00Z\", \"Asia/Seoul\");\n// → Dayjs displaying as 2025-06-15T09:00:00+09:00\n```\n\n### Current Time\n\n| Method           | Returns | Description                              |\n| ---------------- | ------- | ---------------------------------------- |\n| `now(timezone?)` | `Dayjs` | Current moment in the specified timezone |\n\n```typescript\nDayjsUtil.now(); // current time in UTC\nDayjsUtil.now(\"Asia/Seoul\"); // current time in Seoul\nDayjsUtil.now(\"America/New_York\"); // current time in New York\n```\n\n### Conversion\n\n| Method                     | Returns | Description                                           |\n| -------------------------- | ------- | ----------------------------------------------------- |\n| `convertToUTCDate(date?)`  | `Date`  | Timezone conversion → UTC. For **timed events**.      |\n| `stripTimezoneToUTC(str?)` | `Date`  | Preserve time, set tz to UTC. For **all-day events**. |\n| `epoch()`                  | `Date`  | Returns `1970-01-01T00:00:00Z`. Sentinel value.       |\n\n```typescript\n// convertToUTCDate: proper timezone conversion\nDayjsUtil.convertToUTCDate(\"2025-06-15T15:00:00+09:00\");\n// → Date(2025-06-15T06:00:00.000Z)\n\n// stripTimezoneToUTC: keep the wall-clock time, force UTC\nDayjsUtil.stripTimezoneToUTC(\"2025-06-15T00:00:00+09:00\");\n// → Date(2025-06-15T00:00:00.000Z)  ← time preserved, offset discarded\n\n// epoch: sentinel value for \"not applicable\" states\nDayjsUtil.epoch();\n// → Date(1970-01-01T00:00:00.000Z)\n```\n\n### Arithmetic\n\n| Method                                   | Returns | Description                          |\n| ---------------------------------------- | ------- | ------------------------------------ |\n| `add(date, value, unit, timezone?)`      | `Dayjs` | Add time to a date (DST-safe)        |\n| `subtract(date, value, unit, timezone?)` | `Dayjs` | Subtract time from a date (DST-safe) |\n\n> **DST-safe**: Adding 1 day across a DST boundary yields the same wall-clock\n> time the next day, not exactly 24 hours later. Operations are performed in\n> the specified timezone to ensure correctness.\n\n```typescript\n// Add days\nDayjsUtil.add(\"2025-06-15T09:00:00Z\", 3, \"day\");\n// → 2025-06-18T09:00:00Z\n\n// Month-end auto-clamp: Jan 31 + 1 month = Feb 28 (not Mar 3)\nDayjsUtil.add(\"2025-01-31T00:00:00Z\", 1, \"month\");\n// → 2025-02-28T00:00:00Z\n\n// Subtract with timezone\nDayjsUtil.subtract(\"2025-06-15T09:00:00Z\", 2, \"hour\", \"Asia/Seoul\");\n// → Dayjs representing 2025-06-15T16:00:00 KST (was 18:00 KST)\n\n// Add hours\nDayjsUtil.add(\"2025-06-15T09:00:00Z\", 5, \"hour\");\n// → 2025-06-15T14:00:00Z\n```\n\n### Boundaries\n\n| Method                               | Returns | Description                    |\n| ------------------------------------ | ------- | ------------------------------ |\n| `startOf(date, unit, timezone?)`     | `Dayjs` | Start of time unit in timezone |\n| `endOf(date, unit, timezone?)`       | `Dayjs` | End of time unit in timezone   |\n| `startOfDate(date, unit, timezone?)` | `Date`  | Start of time unit as JS Date  |\n| `endOfDate(date, unit, timezone?)`   | `Date`  | End of time unit as JS Date    |\n\n> **Timezone matters**: `startOf(\"day\")` computes midnight in the specified\n> timezone, not UTC midnight. This prevents the common bug where a Seoul user\n> sees events from the wrong day.\n\n```typescript\n// Midnight in Seoul (not UTC midnight!)\nDayjsUtil.startOf(\"2025-06-15T02:00:00Z\", \"day\", \"Asia/Seoul\");\n// → 2025-06-15 00:00:00 KST (= 2025-06-14T15:00:00Z)\n\n// Midnight in UTC\nDayjsUtil.startOf(\"2025-06-15T02:00:00Z\", \"day\");\n// → 2025-06-15 00:00:00 UTC\n\n// End of month\nDayjsUtil.endOf(\"2025-06-15T00:00:00Z\", \"month\");\n// → 2025-06-30T23:59:59.999Z\n\n// Start of week as JS Date (for database queries)\nDayjsUtil.startOfDate(\"2025-06-15T12:00:00Z\", \"week\", \"America/New_York\");\n// → Date representing start of that week in Eastern time\n\n// End of day as JS Date\nDayjsUtil.endOfDate(\"2025-06-15T00:00:00Z\", \"day\", \"Asia/Seoul\");\n// → Date representing 2025-06-15T23:59:59.999 KST\n```\n\n### Formatting\n\n| Method                              | Returns  | Description                     |\n| ----------------------------------- | -------- | ------------------------------- |\n| `formatISOString(date?, tz?)`       | `string` | `2025-01-01T09:00:00+09:00`     |\n| `formatUTCString(date?)`            | `string` | `2025-01-01T00:00:00Z`          |\n| `formatDateOnlyString(date?, tz?)`  | `string` | `2025-01-01` (timezone-aware)   |\n| `extractDateOnlyString(date?)`      | `string` | `2025-01-01` (no tz conversion) |\n| `formatString(date, template, tz?)` | `string` | Custom template with timezone   |\n\n```typescript\nconst date = \"2025-06-15T00:00:00Z\";\n\n// ISO with timezone offset\nDayjsUtil.formatISOString(date, \"Asia/Seoul\");\n// → \"2025-06-15T09:00:00+09:00\"\n\n// UTC string\nDayjsUtil.formatUTCString(date);\n// → \"2025-06-15T00:00:00Z\"\n\n// Date only (timezone-aware — the date may differ across timezones)\nDayjsUtil.formatDateOnlyString(\"2025-06-14T23:00:00Z\", \"Asia/Seoul\");\n// → \"2025-06-15\" (it's already June 15 in Seoul)\n\n// Extract date without timezone conversion (for all-day events)\nDayjsUtil.extractDateOnlyString(\"2025-06-15T09:00:00+09:00\");\n// → \"2025-06-15\" (takes the date as-is from the string)\n\n// Custom format template\nDayjsUtil.formatString(\"2025-06-15T00:00:00Z\", \"YYYY/MM/DD\");\n// → \"2025/06/15\"\n\nDayjsUtil.formatString(\"2025-06-15T00:00:00Z\", \"HH:mm\", \"Asia/Seoul\");\n// → \"09:00\"\n\nDayjsUtil.formatString(\n  \"2025-06-15T00:00:00Z\",\n  \"ddd, MMM D, YYYY\",\n  \"America/New_York\",\n);\n// → \"Sat, Jun 14, 2025\" (still June 14 in New York)\n```\n\n### Timestamps\n\n| Method                      | Returns  | Description                   |\n| --------------------------- | -------- | ----------------------------- |\n| `toUnixSeconds(date?)`      | `number` | Seconds since Unix epoch      |\n| `toUnixMilliseconds(date?)` | `number` | Milliseconds since Unix epoch |\n\n```typescript\nDayjsUtil.toUnixSeconds(\"2025-06-15T00:00:00Z\");\n// → 1750032000\n\nDayjsUtil.toUnixMilliseconds(\"2025-06-15T00:00:00Z\");\n// → 1750032000000\n\n// Current time\nDayjsUtil.toUnixSeconds(); // seconds since epoch (now)\n```\n\n### Duration\n\n| Method                               | Returns  | Description                               |\n| ------------------------------------ | -------- | ----------------------------------------- |\n| `formatDurationString(ms, options?)` | `string` | Human-readable duration from milliseconds |\n\n```typescript\n// Short format (default)\nDayjsUtil.formatDurationString(9_000_000); // \"2h 30min\"\nDayjsUtil.formatDurationString(3_600_000); // \"1h\"\nDayjsUtil.formatDurationString(1_800_000); // \"30min\"\n\n// Long format\nDayjsUtil.formatDurationString(9_000_000, { short: false });\n// → \"2 hours 30 minutes\"\n\nDayjsUtil.formatDurationString(3_600_000, { short: false });\n// → \"1 hour\"\n\n// Edge cases\nDayjsUtil.formatDurationString(0); // \"0min\"\nDayjsUtil.formatDurationString(-3_600_000); // \"1h\" (sign dropped)\n```\n\n### Comparison\n\n| Method                                                        | Returns   | Description                            |\n| ------------------------------------------------------------- | --------- | -------------------------------------- |\n| `isSame(d1?, d2?, unit?, timezone?)`                          | `boolean` | Compare two dates at given granularity |\n| `diff(d1?, d2?, unit?, timezone?)`                            | `number`  | Difference in specified unit           |\n| `isBefore(d1?, d2?, unit?, timezone?)`                        | `boolean` | Check if d1 is before d2               |\n| `isAfter(d1?, d2?, unit?, timezone?)`                         | `boolean` | Check if d1 is after d2                |\n| `isSameOrBefore(d1?, d2?, unit?, timezone?)`                  | `boolean` | Check if d1 is same or before d2       |\n| `isSameOrAfter(d1?, d2?, unit?, timezone?)`                   | `boolean` | Check if d1 is same or after d2        |\n| `isBetween(date, start, end, unit?, inclusivity?, timezone?)` | `boolean` | Check if date falls within range       |\n\n```typescript\n// isSame: compare at different granularities\nDayjsUtil.isSame(\"2025-06-15T10:00:00Z\", \"2025-06-15T22:00:00Z\", \"day\");\n// → true (same day)\n\nDayjsUtil.isSame(\"2025-06-15T10:00:00Z\", \"2025-06-15T22:00:00Z\", \"hour\");\n// → false\n\n// diff: calculate difference\nDayjsUtil.diff(\"2025-06-20\", \"2025-06-15\", \"day\");\n// → 5\n\nDayjsUtil.diff(\"2025-06-15T10:00:00Z\", \"2025-06-15T08:00:00Z\", \"hour\");\n// → 2\n\n// isBefore / isAfter\nDayjsUtil.isBefore(\"2025-06-14\", \"2025-06-15\"); // true\nDayjsUtil.isBefore(\"2025-06-15T23:00\", \"2025-06-15T01:00\", \"day\"); // false (same day)\n\nDayjsUtil.isAfter(\"2025-06-16\", \"2025-06-15\"); // true\n\n// isSameOrBefore / isSameOrAfter\nDayjsUtil.isSameOrBefore(\"2025-06-15\", \"2025-06-15\"); // true\nDayjsUtil.isSameOrAfter(\"2025-06-15\", \"2025-06-14\"); // true\n\n// isBetween: range check with bracket notation\nDayjsUtil.isBetween(\"2025-06-15\", \"2025-06-01\", \"2025-06-30\", \"day\", \"[]\");\n// → true (inclusive on both ends)\n\nDayjsUtil.isBetween(\"2025-06-15\", \"2025-06-15\", \"2025-06-20\");\n// → false (default \"()\" excludes boundaries)\n\nDayjsUtil.isBetween(\"2025-06-15\", \"2025-06-15\", \"2025-06-20\", null, \"[]\");\n// → true (inclusive — to set inclusivity without unit, pass null for unit)\n```\n\n#### Bracket notation (`isBetween`)\n\n| Notation | Start     | End       | Meaning                        |\n| -------- | --------- | --------- | ------------------------------ |\n| `()`     | exclusive | exclusive | Default                        |\n| `[]`     | inclusive | inclusive | Both boundaries match          |\n| `[)`     | inclusive | exclusive | Start matches, end does not    |\n| `(]`     | exclusive | inclusive | Start does not match, end does |\n\n### Time Manipulation\n\n| Method                                | Returns   | Description                                         |\n| ------------------------------------- | --------- | --------------------------------------------------- |\n| `copyTime(source, target, timezone?)` | `Dayjs`   | Copy time from one date onto another's calendar day |\n| `isMidnight(date, timezone?)`         | `boolean` | Check if time is exactly 00:00:00.000               |\n\n```typescript\n// copyTime: drag-and-drop event to a new day while keeping its time\nDayjsUtil.copyTime(\"2025-06-15T14:30:00Z\", \"2025-06-20T00:00:00Z\");\n// → 2025-06-20T14:30:00Z (June 20 with 14:30 from June 15)\n\n// With timezone: preserves wall-clock time in that timezone\nDayjsUtil.copyTime(\n  \"2025-06-15T14:30:00+09:00\",\n  \"2025-06-20T00:00:00+09:00\",\n  \"Asia/Seoul\",\n);\n// → Dayjs representing 2025-06-20 14:30:00 KST\n\n// isMidnight: all-day event detection\nDayjsUtil.isMidnight(\"2025-06-15T00:00:00Z\"); // true\nDayjsUtil.isMidnight(\"2025-06-15T09:00:00Z\"); // false\nDayjsUtil.isMidnight(\"2025-06-14T15:00:00Z\", \"Asia/Seoul\"); // true (midnight KST)\nDayjsUtil.isMidnight(\"2025-06-15T00:00:00Z\", \"Asia/Seoul\"); // false (09:00 KST)\n```\n\n### Calendar Domain\n\n| Method                               | Returns    | Description                              |\n| ------------------------------------ | ---------- | ---------------------------------------- |\n| `dayOfWeekString(date, timezone?)`   | `RRuleDay` | RRULE day-of-week code (SU, MO, ... SA)  |\n| `remainingDays(from, to, timezone?)` | `number`   | Remaining whole days (rounds up partial) |\n\n```typescript\n// dayOfWeekString: get RRULE day code\nDayjsUtil.dayOfWeekString(\"2025-06-16T00:00:00Z\"); // \"MO\" (Monday)\nDayjsUtil.dayOfWeekString(\"2025-06-15T00:00:00Z\"); // \"SU\" (Sunday)\nDayjsUtil.dayOfWeekString(\"2025-06-20T00:00:00Z\"); // \"FR\" (Friday)\n\n// RRULE codes: \"SU\" | \"MO\" | \"TU\" | \"WE\" | \"TH\" | \"FR\" | \"SA\"\n\n// remainingDays: rounds up partial days\nDayjsUtil.remainingDays(\"2025-06-15\", \"2025-06-20\"); // 5\nDayjsUtil.remainingDays(\"2025-06-15\", \"2025-06-15T01:00:00Z\"); // 1 (partial → rounds up)\nDayjsUtil.remainingDays(\"2025-06-15\", \"2025-06-15\"); // 0 (exact same moment)\nDayjsUtil.remainingDays(\"2025-06-20\", \"2025-06-15\"); // -5 (negative = past)\n```\n\n### Validation\n\n| Method                           | Returns   | Description                                   |\n| -------------------------------- | --------- | --------------------------------------------- |\n| `isValidDateFormat(str, format)` | `boolean` | Validate string against `DATE_FORMAT` pattern |\n\n```typescript\nimport { DayjsUtil, DATE_FORMAT } from \"@brandonwie/dayjs-util\";\n\nDayjsUtil.isValidDateFormat(\"2025-06-15\", DATE_FORMAT.DATE); // true\nDayjsUtil.isValidDateFormat(\"2025-06-15T10:00:00Z\", DATE_FORMAT.DATETIME_UTC); // true\nDayjsUtil.isValidDateFormat(\n  \"2025-06-15T10:00:00+09:00\",\n  DATE_FORMAT.DATETIME_OFFSET,\n); // true\nDayjsUtil.isValidDateFormat(\"not-a-date\", DATE_FORMAT.DATE); // false\n```\n\n#### Supported `DATE_FORMAT` constants\n\n| Constant             | Pattern                         | Example                         |\n| -------------------- | ------------------------------- | ------------------------------- |\n| `DATE`               | `YYYY-MM-DD`                    | `2025-01-01`                    |\n| `DATETIME`           | `YYYY-MM-DDTHH:mm:ss`           | `2025-01-01T10:00:00`           |\n| `DATETIME_UTC`       | `YYYY-MM-DDTHH:mm:ssZ`          | `2025-01-01T10:00:00Z`          |\n| `DATETIME_OFFSET`    | `YYYY-MM-DDTHH:mm:ss±HH:mm`     | `2025-01-01T10:00:00+09:00`     |\n| `DATETIME_MS`        | `YYYY-MM-DDTHH:mm:ss.SSS`       | `2025-01-01T10:00:00.000`       |\n| `DATETIME_MS_UTC`    | `YYYY-MM-DDTHH:mm:ss.SSSZ`      | `2025-01-01T10:00:00.000Z`      |\n| `DATETIME_MS_OFFSET` | `YYYY-MM-DDTHH:mm:ss.SSS±HH:mm` | `2025-01-01T10:00:00.000+09:00` |\n\n## EventDateHandler (Calendar Events)\n\n> **Optional import** — only adds ~7KB when used. Safe to ignore if you don't work with calendar data.\n\nCalendar APIs (Google, Microsoft, iCal) send event dates in different formats.\nEventDateHandler normalizes them to UTC `Date` objects for consistent storage.\n\n### The two kinds of calendar events\n\n| Kind    | What it means                       | Example                        | Goal                               |\n| ------- | ----------------------------------- | ------------------------------ | ---------------------------------- |\n| All-day | A calendar **date**, not a moment   | \"June 15th\" (no specific time) | Store as midnight UTC on that date |\n| Timed   | A specific **moment** in a timezone | \"June 15, 9:00 AM Seoul time\"  | Convert to UTC point-in-time       |\n\n### Import\n\n```typescript\nimport { EventDateHandler } from \"@brandonwie/dayjs-util/event\";\n// or: import { EventDateHandler } from \"@brandonwie/dayjs-util\";\n```\n\n### `toAllDayUTC(start, end)`\n\nExtracts the **date** from any format and returns midnight UTC.\nTime and timezone in the input are ignored — only the date matters.\n\n```typescript\n// All of these produce the same result:\nEventDateHandler.toAllDayUTC(\"2025-06-15\", \"2025-06-16\"); // Google Calendar\nEventDateHandler.toAllDayUTC(\n  \"2025-06-15T00:00:00+09:00\",\n  \"2025-06-16T00:00:00+09:00\",\n); // offset string\nEventDateHandler.toAllDayUTC(\"2025-06-15T00:00:00Z\", \"2025-06-16T00:00:00Z\"); // UTC\nEventDateHandler.toAllDayUTC(\"2025-06-15T00:00:00\", \"2025-06-16T00:00:00\"); // Microsoft Graph\nEventDateHandler.toAllDayUTC(\"20250615\", \"20250616\"); // iCal compact\n// → { start: Date(2025-06-15T00:00:00Z), end: Date(2025-06-16T00:00:00Z), timezone: \"UTC\" }\n```\n\n### `toTimedUTC(start, end, timezone)`\n\nConverts a timed event to UTC. The `timezone` parameter is used for **parsing**\nwhen the input string has no embedded offset.\n\n```typescript\n// String WITH offset — offset used for conversion, timezone stored as metadata\nEventDateHandler.toTimedUTC(\n  \"2025-06-15T09:00:00+09:00\",\n  \"2025-06-15T10:00:00+09:00\",\n  \"Asia/Seoul\",\n);\n// → { start: Date(2025-06-15T00:00:00Z), end: Date(2025-06-15T01:00:00Z), timezone: \"Asia/Seoul\" }\n\n// String WITHOUT offset — timezone used to interpret the time\nEventDateHandler.toTimedUTC(\n  \"2025-06-15T09:00:00\",\n  \"2025-06-15T10:00:00\",\n  \"Asia/Seoul\",\n);\n// → { start: Date(2025-06-15T00:00:00Z), end: Date(2025-06-15T01:00:00Z), timezone: \"Asia/Seoul\" }\n\n// UTC string — Z does the work\nEventDateHandler.toTimedUTC(\n  \"2025-06-15T00:00:00Z\",\n  \"2025-06-15T01:00:00Z\",\n  \"UTC\",\n);\n// → { start: Date(2025-06-15T00:00:00Z), end: Date(2025-06-15T01:00:00Z), timezone: \"UTC\" }\n```\n\n### `normalize(params)`\n\nDispatches to `toAllDayUTC` or `toTimedUTC` based on the `isAllDay` flag.\nReturns `null` when start or end is missing.\n\n```typescript\n// Timed event (default when isAllDay is omitted)\nconst result = EventDateHandler.normalize({\n  start: \"2025-06-15T09:00:00+09:00\",\n  end: \"2025-06-15T10:00:00+09:00\",\n  timezone: \"Asia/Seoul\",\n});\n// → { start: Date(...), end: Date(...), timezone: \"Asia/Seoul\" }\n\n// All-day event\nconst allDay = EventDateHandler.normalize({\n  start: \"2025-06-15\",\n  end: \"2025-06-16\",\n  timezone: \"Asia/Seoul\",\n  isAllDay: true,\n});\n// → { start: Date(2025-06-15T00:00:00Z), end: Date(2025-06-16T00:00:00Z), timezone: \"UTC\" }\n\n// Missing dates\nEventDateHandler.normalize({\n  start: undefined,\n  end: undefined,\n  timezone: \"UTC\",\n});\n// → null\n```\n\n### Supported input formats\n\n| Format            | Example                       | All-day | Timed | Source                   |\n| ----------------- | ----------------------------- | ------- | ----- | ------------------------ |\n| Plain date        | `\"2025-06-15\"`                | Yes     | Yes\\* | Google Calendar, Cal.com |\n| Compact iCal      | `\"20250615\"`                  | Yes     | Yes\\* | RFC 5545 VALUE=DATE      |\n| Datetime + offset | `\"2025-06-15T09:00:00+09:00\"` | Yes     | Yes   | Google Calendar          |\n| Datetime UTC      | `\"2025-06-15T09:00:00Z\"`      | Yes     | Yes   | Calendly                 |\n| Datetime bare     | `\"2025-06-15T09:00:00\"`       | Yes     | Yes   | Microsoft Graph, iCal    |\n| Date object       | `new Date(...)`               | Yes     | Yes   | Any                      |\n\n\\*Date-only strings in timed events are treated as midnight in the given timezone.\n\n### API summary\n\n| Method                       | Returns                        | Description                  |\n| ---------------------------- | ------------------------------ | ---------------------------- |\n| `toAllDayUTC(start, end)`    | `NormalizedEventDates`         | Extract date → midnight UTC  |\n| `toTimedUTC(start, end, tz)` | `NormalizedEventDates`         | Convert to UTC point-in-time |\n| `normalize(params)`          | `NormalizedEventDates \\| null` | Dispatch by `isAllDay` flag  |\n\n## Migration Guide: `new Date()` → DayjsUtil\n\n| Before                             | After                                   | Why                                 |\n| ---------------------------------- | --------------------------------------- | ----------------------------------- |\n| `new Date()`                       | `DayjsUtil.utc().toDate()`              | Explicit UTC, no local tz ambiguity |\n| `new Date(str)`                    | `DayjsUtil.utc(str).toDate()`           | Consistent parsing                  |\n| `new Date(str).toISOString()`      | `DayjsUtil.formatUTCString(str)`        | Same result, cleaner API            |\n| `date.toISOString().split('T')[0]` | `DayjsUtil.extractDateOnlyString(date)` | Handles all input types             |\n| `new Date(0)`                      | `DayjsUtil.epoch()`                     | Self-documenting sentinel           |\n| `Date.now()`                       | `DayjsUtil.now().valueOf()`             | Timezone-aware \"now\"                |\n| `d1.getTime() - d2.getTime()`      | `DayjsUtil.diff(d1, d2, 'ms')`          | Readable, unit-aware                |\n| `d1 < d2`                          | `DayjsUtil.isBefore(d1, d2)`            | Explicit, unit-aware                |\n| `d1 >= d2`                         | `DayjsUtil.isSameOrAfter(d1, d2)`       | Timezone-safe                       |\n| Manual offset math                 | `DayjsUtil.tz(date, 'Asia/Seoul')`      | IANA timezone, DST-safe             |\n| `dayjs(str).tz(tz)`                | `DayjsUtil.tzParse(str, tz)`            | Correct semantics (see above)       |\n| `dayjs(d).add(1, 'day')`           | `DayjsUtil.add(d, 1, 'day', tz)`        | DST-safe, timezone explicit         |\n| `dayjs(d).startOf('day')`          | `DayjsUtil.startOf(d, 'day', tz)`       | Midnight in correct timezone        |\n| `Math.floor(d / 1000)`             | `DayjsUtil.toUnixSeconds(d)`            | Clean API                           |\n| `date.getHours() === 0 && ...`     | `DayjsUtil.isMidnight(date, tz)`        | Timezone-aware midnight check       |\n\n## Design Decisions\n\n- **Static class** — no instantiation needed, dayjs instances created per-call (immutable, ~0.01ms)\n- **Plugins loaded once** — utc, timezone, isSameOrAfter, isSameOrBefore, isBetween, duration registered at import time\n- **Peer dependency on dayjs** — consumers control the version, no duplication\n- **Dual CJS/ESM** — works in Node.js, browsers, and bundlers\n- **Tree-shakeable** — `sideEffects: false` for optimal bundling; `EventDateHandler` can be imported separately via `/event` entry point\n\n## How DST is Handled\n\nThis library is DST-safe because every timezone-aware method applies the timezone\n**before** performing operations. The actual DST resolution is delegated down a chain:\n\n```\nDayjsUtil → dayjs timezone plugin → Intl.DateTimeFormat → OS/ICU timezone data\n```\n\nThis library has no built-in timezone database. It relies on the JavaScript\nruntime's [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat)\nAPI, which reads DST rules from the operating system's copy of the\n[IANA timezone database](https://www.iana.org/time-zones).\n\n### Why the `timezone` parameter matters for DST\n\nThe offset in an ISO 8601 string (e.g., `-05:00`) resolves the input to an exact\nUTC instant. The `timezone` parameter tells arithmetic operations which\ncalendar/clock rules to follow. These are different jobs:\n\n```typescript\n// Frontend sends a New York time with offset (EST = -05:00)\nDayjsUtil.add(\"2025-03-09T01:00:00-05:00\", 1, \"day\", \"America/New_York\");\n// → March 10, 01:00 AM EDT (-04:00, DST kicked in)\n// → Internally: 2025-03-10T05:00:00Z (23 real hours, not 24)\n\n// Without timezone: raw +24h math\nDayjsUtil.add(\"2025-03-09T01:00:00-05:00\", 1, \"day\");\n// → March 10, 02:00 AM EDT (wall-clock drifted by 1 hour)\n```\n\nFor a calendar showing \"daily event at 1:00 AM\", that 1-hour drift matters.\n\n### Which methods are DST-aware\n\n| Method                            | Why DST matters                                              |\n| --------------------------------- | ------------------------------------------------------------ |\n| `add` / `subtract`                | Wall-clock time preserved across DST boundaries              |\n| `startOf` / `endOf`               | Midnight depends on the timezone's DST state                 |\n| `isSame` / `isBefore` / `isAfter` | \"Same day\" boundary shifts with DST                          |\n| `remainingDays`                   | Uses calendar-day diff, not `ms / 86400000`                  |\n| `toTimedUTC` (EventDateHandler)   | `dayjs.tz()` resolves DST when parsing bare datetime strings |\n\n### Edge cases\n\n| Case                                                               | Behavior                                                                     |\n| ------------------------------------------------------------------ | ---------------------------------------------------------------------------- |\n| **Ambiguous time** (fall-back overlap, e.g., 1:30 AM occurs twice) | dayjs picks the first occurrence (pre-transition)                            |\n| **Non-existent time** (spring-forward gap, e.g., 2:30 AM skipped)  | dayjs rolls forward to the next valid time                                   |\n| **Stale timezone data**                                            | Requires updating Node.js or OS; this library does not ship its own database |\n\n> **Note:** Timezones that do not observe DST (e.g., `Asia/Seoul`, `UTC`) are\n> unaffected. The `timezone` parameter still works — it just applies a fixed offset.\n\n## Breaking Changes in v0.4.0\n\n### EventDateHandler API redesigned\n\n| v0.3.x                                   | v0.4.0                       | Notes                                                                                          |\n| ---------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------- |\n| `processAllDayEventDates(start, end)`    | `toAllDayUTC(start, end)`    | Accepts `DateInput` (not just `string`). Always returns midnight.                              |\n| `processTimedEventDates(start, end, tz)` | `toTimedUTC(start, end, tz)` | `tz` now used for parsing offset-less strings.                                                 |\n| `computeScheduleDates(params)`           | `normalize(params)`          | Params: `start`/`end`/`timezone` (not `startAt`/`endAt`/`timeZone`). Returns object or `null`. |\n\n```typescript\n// v0.3.x\nconst { startAt, endAt, zone } = EventDateHandler.processAllDayEventDates(\n  start,\n  end,\n);\nconst [s, e, z] = EventDateHandler.computeScheduleDates({\n  startAt,\n  endAt,\n  timeZone,\n  isAllDay,\n});\n\n// v0.4.0\nconst { start, end, timezone } = EventDateHandler.toAllDayUTC(\n  startDate,\n  endDate,\n);\nconst result = EventDateHandler.normalize({ start, end, timezone, isAllDay });\nif (result) {\n  const { start, end, timezone } = result;\n}\n```\n\n## References\n\n- [dayjs](https://day.js.org/) — lightweight date library this package wraps\n- [Google Calendar API — Create Events](https://developers.google.com/workspace/calendar/api/guides/create-events) — `date` vs `dateTime` field distinction\n- [Microsoft Graph — Event Resource Type](https://learn.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0) — `DateTimeTimeZone` type with separate timezone field\n- [RFC 5545 — iCalendar](https://datatracker.ietf.org/doc/html/rfc5545) — `VALUE=DATE` vs `VALUE=DATE-TIME`, `TZID` parameter\n- [RFC 3339 — Date and Time on the Internet](https://datatracker.ietf.org/doc/html/rfc3339) — timestamp format used by Google Calendar and Calendly\n- [Calendly API — Scheduled Events](https://developer.calendly.com/api-docs/d7114b2e5a5a2-list-events) — always-UTC timestamps with `Z` suffix\n- [Cal.com API — Schedules](https://cal.com/docs/api-reference/v2/schedules/get-a-schedule) — separate date, time, and timezone fields\n- [IANA Time Zone Database](https://www.iana.org/time-zones) — authoritative source for timezone identifiers (e.g., `Asia/Seoul`)\n\n## License\n\nMIT\n","readmeFilename":"README.md"}