> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# Migrate Moment.js sang Day.js

## 1. Learning Objectives

By the end of this article, you'll be able to:

- **Giảm bundle size hơn 97%** bằng cách thay thế `moment.js` (\~230KB) bằng `dayjs` (\~2KB minified + gzipped).
- **Chuyển đổi cú pháp song song** giữa Moment.js và Day.js cho tất cả các thao tác thường gặp (parse, format, add/subtract, diff, timezone).
- **Kích hoạt các Day.js plugins** (`customParseFormat`, `utc`, `timezone`, `relativeTime`, `isBetween`) để tái lập 100% tính năng của Moment.js.
- **Xử lý triệt để phụ thuộc Moment.js** trong các plugin UI legacy như `bootstrap-daterangepicker`.

***

## 2. Plan Availability & Prerequisites

Kỹ thuật migration này áp dụng cho mọi dự án JavaScript frontend/backend (Vue.js, React, Node.js, Webpack, Vite, hoặc AEM Clientlibs).

| Thư viện      | Version khuyến nghị | Bundle Size (Minified + Gzip)                 | Trạng thái                    |
| ------------- | ------------------- | --------------------------------------------- | ----------------------------- |
| **Moment.js** | `v2.30.1` (latest)  | \~71KB (chưa locale) / \~231KB (full locales) | Maintenance mode (Legacy)     |
| **Day.js**    | `v1.11.13` (latest) | \~2KB (core) + \~1KB/plugin                   | Active development (Standard) |

:::note\[Tương thích hoàn toàn]
Day.js được thiết kế với API tương thích gần như 1:1 với Moment.js. Nếu bạn đã quen thuộc với Moment.js, bạn sẽ không cần phải học lại cú pháp mới.
:::

***

## 3. What Is Day.js? — Definition & Expansion

> **Day.js** là thư viện JavaScript xử lý ngày tháng bất biến (immutable), dung lượng siêu nhẹ (2KB) với API tương thích 1:1 với Moment.js.

Trong nhiều năm, Moment.js là chuẩn mực cho xử lý date/time trên web. Tuy nhiên, kiến trúc ban đầu của Moment.js mắc 2 hạn chế lớn trong phát triển web hiện đại:

1. **Dung lượng quá lớn:** Moment chứa sẵn toàn bộ locale và không hỗ trợ Tree-shaking.
2. **Mutable State:** Thực hiện `.add()` hay `.subtract()` trên một instance sẽ làm thay đổi trực tiếp Date object ban đầu, dẫn đến vô số bug ẩn khó phát hiện.

_Nhưng Day.js không chỉ dừng lại ở việc thay thế format ngày tháng cơ bản — bạn còn có thể dùng nó để chuyển đổi Timezone phức tạp, tính toán khoảng thời gian tương đối (relative time), và làm cầu nối (shim) cho các UI plugin legacy._

***

## 4. Types / Variants — Architecture Comparison

### Mutable Architecture (Moment.js)

Mọi phương thức biến đổi trong Moment.js đều làm thay đổi (mutate) chính đối tượng hiện tại. Bạn buộc phải gọi `.clone()` nếu muốn giữ lại giá trị ban đầu.

```js title="moment-mutation-example.js"
const now = moment('2026-08-05');
const tomorrow = now.add(1, 'day');

console.log(now.format('YYYY-MM-DD'));      // 2026-08-06 (Bị thay đổi ngoài ý muốn!)
console.log(tomorrow.format('YYYY-MM-DD')); // 2026-08-06
```

### Immutable Architecture (Day.js)

Mọi thao tác trên Day.js đều trả về một **instance mới hoàn toàn**. Instance ban đầu giữ nguyên giá trị, đảm bảo tính toàn vẹn dữ liệu trong Vuex/Redux store và pure functions.

```js title="dayjs-immutability-example.js"
const now = dayjs('2026-08-05');
const tomorrow = now.add(1, 'day');

console.log(now.format('YYYY-MM-DD'));      // 2026-08-05 (An toàn tuyệt đối!)
console.log(tomorrow.format('YYYY-MM-DD')); // 2026-08-06
```

***

## 5. How to Enable / Activate (Installation & Plugin Setup)

Day.js sử dụng kiến trúc **Plugin-based**. Core library chỉ chứa các hàm cơ bản (2KB). Muốn dùng tính năng nâng cao của Moment, bạn chỉ cần nạp plugin tương ứng.

### Step 1: Cài đặt package

```bash
# npm
npm install dayjs

# pnpm
pnpm add dayjs
```

### Step 2: Kích hoạt Plugins cần thiết

```js title="src/utils/dayjs-config.js"
import dayjs from 'dayjs';

// Import các plugin phổ biến thay thế Moment.js
import customParseFormat from 'dayjs/plugin/customParseFormat';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
import isBetween from 'dayjs/plugin/isBetween';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import 'dayjs/locale/vi'; // Import tiếng Việt nếu cần

// Extend plugin vào Day.js
dayjs.extend(customParseFormat);
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
dayjs.extend(isBetween);
dayjs.extend(localizedFormat);

// Set locale mặc định
dayjs.locale('vi');

export default dayjs;
```

***

## 6. Using It in Practice — Side-by-Side Comparison

Dưới đây là bảng so sánh cú pháp 1:1 giữa Moment.js và Day.js cho các tác vụ lập trình hàng ngày.

### 6.1 Parse & Format Ngày Tháng


Day.js (Recommended)Moment.js (Legacy)

```js title="dayjs-parse-format.js"
import dayjs from 'dayjs';

// Parse hiện tại
const now = dayjs();

// Parse string
const d1 = dayjs('2026-08-05');
const d2 = dayjs('05/08/2026', 'DD/MM/YYYY'); // Cần plugin customParseFormat

// Format output
console.log(now.format('YYYY-MM-DD HH:mm:ss')); // "2026-08-05 14:30:00"
console.log(now.format('DD/MM/YYYY'));          // "05/08/2026"
```

```js title="moment-parse-format.js"
import moment from 'moment';

// Parse hiện tại
const now = moment();

// Parse string
const d1 = moment('2026-08-05');
const d2 = moment('05/08/2026', 'DD/MM/YYYY');

// Format output
console.log(now.format('YYYY-MM-DD HH:mm:ss')); // "2026-08-05 14:30:00"
console.log(now.format('DD/MM/YYYY'));          // "05/08/2026"
```

### 6.2 Cộng / Trừ & Đầu / Cuối Khoảng Thời Gian


Day.jsMoment.js

```js title="dayjs-manipulate.js"
// Cộng / Trừ thời gian (Trả về instance MỚI)
const nextWeek = dayjs().add(7, 'day');
const lastMonth = dayjs().subtract(1, 'month');

// Đầu ngày / Cuối tháng
const startOfDay = dayjs().startOf('day');   // 2026-08-05 00:00:00
const endOfMonth = dayjs().endOf('month');   // 2026-08-31 23:59:59
```

```js title="moment-manipulate.js"
// Cộng / Trừ thời gian (Cần clone() nếu không muốn mutate!)
const nextWeek = moment().clone().add(7, 'day');
const lastMonth = moment().clone().subtract(1, 'month');

// Đầu ngày / Cuối tháng
const startOfDay = moment().clone().startOf('day');
const endOfMonth = moment().clone().endOf('month');
```

### 6.3 So Sánh & Tính Khoảng Cách (Diff, IsBetween)


Day.jsMoment.js

```js title="dayjs-query.js"
import isBetween from 'dayjs/plugin/isBetween';
dayjs.extend(isBetween);

const d1 = dayjs('2026-08-01');
const d2 = dayjs('2026-08-10');
const target = dayjs('2026-08-05');

// So sánh
console.log(d1.isBefore(d2)); // true
console.log(d1.isSame('2026-08-01', 'day')); // true
console.log(target.isBetween(d1, d2)); // true (Cần plugin isBetween)

// Tính khoảng cách
const diffDays = d2.diff(d1, 'day'); // 9
```

```js title="moment-query.js"
const d1 = moment('2026-08-01');
const d2 = moment('2026-08-10');
const target = moment('2026-08-05');

// So sánh
console.log(d1.isBefore(d2)); // true
console.log(d1.isSame('2026-08-01', 'day')); // true
console.log(target.isBetween(d1, d2)); // true

// Tính khoảng cách
const diffDays = d2.diff(d1, 'day'); // 9
```

### 6.4 UTC, Timezone & Relative Time


Day.jsMoment.js

```js title="dayjs-advanced.js"
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);

// UTC & Timezone
const utcTime = dayjs.utc('2026-08-05 10:00');
const vnTime = utcTime.tz('Asia/Ho_Chi_Minh');

// Relative time (fromNow)
console.log(dayjs('2026-08-01').fromNow()); // "4 ngày trước" (với locale vi)
```

```js title="moment-advanced.js"
import moment from 'moment-timezone';

// UTC & Timezone
const utcTime = moment.utc('2026-08-05 10:00');
const vnTime = utcTime.tz('Asia/Ho_Chi_Minh');

// Relative time
console.log(moment('2026-08-01').fromNow()); // "4 days ago"
```

***

## 7. Standout Feature: Handling `daterangepicker` Plugin

Một thách thức lớn khi refactor dự án legacy (đặc biệt các dự án AEM hoặc jQuery/Bootstrap) là plugin **`daterangepicker`** (như `bootstrap-daterangepicker`). Thư viện này phụ thuộc cứng vào `window.moment`.

Dưới đây là 3 chiến lược xử lý triệt để:

### Chiến lược 1: Shim / Webpack Alias + `badMutable` (Giải pháp Re-architecture)

Nếu dự án dùng Webpack/Vite và bạn không muốn sửa lại code khởi tạo `daterangepicker`, bạn có thể dùng plugin `badMutable` của Day.js để biến Day.js thành mutable (giống Moment) và alias `moment` sang `dayjs`.

```js title="src/utils/moment-shim.js"
import dayjs from 'dayjs';
import badMutable from 'dayjs/plugin/badMutable';
import customParseFormat from 'dayjs/plugin/customParseFormat';

dayjs.extend(badMutable);
dayjs.extend(customParseFormat);

// Gán vào window cho daterangepicker truy cập
window.moment = dayjs;

export default dayjs;
```

Trong file cấu hình Webpack:

```js title="webpack.config.js"
module.exports = {
  resolve: {
    alias: {
      moment$: path.resolve(__dirname, 'src/utils/moment-shim.js'),
    },
  },
};
```

### Chiến lược 2: Adapter Pattern cho DateRangePicker Input

Nếu dự án không dùng Webpack Alias, bạn tạo Adapter chuyển đổi giữa Day.js object và native `Date` object / String format mà `daterangepicker` chấp nhận:

```js title="src/components/DateRangePickerAdapter.js"
import dayjs from 'dayjs';

export function initDateRangePicker($element, options = {}) {
  const defaultOptions = {
    startDate: options.startDate ? dayjs(options.startDate).toDate() : dayjs().startOf('month').toDate(),
    endDate: options.endDate ? dayjs(options.endDate).toDate() : dayjs().endOf('month').toDate(),
    locale: {
      format: 'DD/MM/YYYY',
      separator: ' - ',
    },
  };

  // Khởi tạo daterangepicker
  $element.daterangepicker({ ...defaultOptions, ...options }, (start, end, label) => {
    // Callback trả về Day.js instance cho application logic
    const dayjsStart = dayjs(start.toDate());
    const dayjsEnd = dayjs(end.toDate());
    
    if (typeof options.onSelect === 'function') {
      options.onSelect(dayjsStart, dayjsEnd, label);
    }
  });
}
```

### Chiến lược 3: Thay thế `daterangepicker` bằng Thư viện Modern Không Phụ Thuộc Moment

Về lâu dài, cách sạch nhất là thay thế `bootstrap-daterangepicker` bằng giải pháp hiện đại không phụ thuộc Moment.js:

| Thư viện thay thế          | Size   | Đặc điểm                                                |
| -------------------------- | ------ | ------------------------------------------------------- |
| **Flatpickr** (Range mode) | \~15KB | Siêu nhẹ, zero dependency, hỗ trợ Vue/React             |
| **Litepicker**             | \~10KB | Hỗ trợ Range selection cực tốt, không cần jQuery/Moment |
| **Vanilla JS Datepicker**  | \~8KB  | Tương thích Bootstrap 5, không phụ thuộc jQuery         |

:::warning\[Security & Compatibility Caveat]
Khi dùng `badMutable` shim `window.moment`, chỉ áp dụng cho phạm vi plugin legacy như `daterangepicker`. Trong code ứng dụng mới viết, **luôn khuyến khích dùng Day.js chuẩn (immutable)** để tránh lỗi sai lệch trạng thái date.
:::

***

## 8. Security & Privacy Considerations

- **Immutability ngăn ngừa Data Loss & Prototype Pollution:** Việc dùng Day.js immutable tránh việc các hàm tiện ích vô tình chỉnh sửa đối tượng Date được dùng chung ở global state (Vuex/Pinia/Redux).
- **Validate Input Strict:** Khi nhận chuỗi ngày tháng từ người dùng hoặc API bên ngoài, luôn dùng `customParseFormat` để parse kèm format cụ thể thay vì parse tự do, tránh lỗi lệch múi giờ hoặc format sai.
- **Tránh Memory Leak trong Single Page Apps:** Khi lặp lại các thao tác tính toán ngày tháng trong watcher hoặc render loop, các instance Day.js nhẹ (2KB) tạo ra ít áp lực cho Garbage Collector hơn rất nhiều so với Moment.js.

***

## 9. Creating Custom Migration Adapters (No Code Rewrite Required)

Để migrate một dự án lớn chứa hàng trăm file đang gọi `moment(...)` mà không phải sửa lại từng dòng code, bạn có thể tạo một **Custom Helper Module** đóng vai trò Facade:

```js title="src/utils/date-facade.js"
import dayjs from './dayjs-config'; // Cấu hình đã nạp đủ plugin

/**
 * Facade function tương thích với signature của moment()
 */
export function dateHelper(input, format, strict) {
  if (!input) return dayjs();
  if (format) return dayjs(input, format, strict);
  return dayjs(input);
}

// Attach các helper utility giống moment
dateHelper.utc = dayjs.utc;
dateHelper.tz = dayjs.tz;
dateHelper.isDayjs = dayjs.isDayjs;

export default dateHelper;
```

Khi đó, trong các file component cũ, bạn chỉ cần thay đổi dòng import:

```js
// Trước khi migrate:
// import moment from 'moment';

// Sau khi migrate:
import moment from '@/utils/date-facade';

// Mọi code bên dưới giữ nguyên 100%!
const formatted = moment('2026-08-05').format('DD/MM/YYYY');
```

***

## 10. Comparison: Moment.js vs Day.js

| Criteria                    | Moment.js                             | Day.js                             |
| --------------------------- | ------------------------------------- | ---------------------------------- |
| **Bundle Size**             | \~231KB (gzipped with locales)        | \~2KB (core) + plugin theo nhu cầu |
| **Mutability**              | Mutable (Dễ gây side-effects & bug)   | Immutable (An toàn tuyệt đối)      |
| **Tree-shaking**            | Không hỗ trợ                          | Hỗ trợ từng plugin riêng lẻ        |
| **Kiến trúc**               | Monolithic                            | Plugin-based modular               |
| **Maintenance Status**      | Maintenance mode (Không thêm feature) | Active Development                 |
| **DateRangePicker Support** | Native (Mặc định)                     | Cần Adapter / Shim / `badMutable`  |

> **Takeaway:** _Moment.js là di sản quá khứ; Day.js là tiêu chuẩn hiện tại — nhỏ hơn 97%, immutable và hoàn toàn linh hoạt._

***

## 11. Lesson Reflection — Think About It

1. Kiểm tra lại dự án hiện tại của bạn: `moment.js` đang chiếm bao nhiêu % tổng dung lượng bundle JS? (Dùng `webpack-bundle-analyzer` hoặc `source-map-explorer`).
2. Có bao nhiêu nơi trong codebase của bạn đang gọi `moment().add()` hoặc `moment().subtract()` mà **không** gọi `.clone()` trước đó? Đó có phải là nguyên nhân của những bug ngày tháng khó hiểu?
3. Bạn sẽ chọn giải pháp nào cho `daterangepicker`: Dùng `badMutable` shim tạm thời hay lên kế hoạch thay thế bằng `Litepicker` / `Flatpickr`?

***

## 12. What’s Next — Next Steps & Resources

Bài tiếp theo trong chuỗi tối ưu hóa Frontend: **Tối ưu Clientlibs & Webpack Build trong AEM AMS 6.5**.

- [Trang chủ chính thức của Day.js](https://day.js.org/)
- [Danh sách Plugins đầy đủ của Day.js](https://day.js.org/docs/en/plugin/plugin)
- [Plugin-free replacement guide: You Dont Need Momentjs](https://github.com/you-dont-need/You-Dont-Need-Momentjs)
- [Litepicker — Thư viện Date Range Picker không cần Moment](https://litepicker.com/)

***
