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, and this page is available as Markdown at https://nhanbe.id.vn/others/posts/migrate-moment-to-dayjs.md.

Migrate Moment.js sang Day.js - developer documentation and reference notes for Migrate Moment.js sang Day.js.

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ệnVersion khuyến nghịBundle Size (Minified + Gzip)Trạng thái
Moment.jsv2.30.1 (latest)~71KB (chưa locale) / ~231KB (full locales)Maintenance mode (Legacy)
Day.jsv1.11.13 (latest)~2KB (core) + ~1KB/pluginActive development (Standard)
[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.

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.

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

# npm
npm install dayjs

# pnpm
pnpm add dayjs

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

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

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"

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

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

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

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

6.4 UTC, Timezone & Relative Time

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)

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.

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:

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:

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)~15KBSiêu nhẹ, zero dependency, hỗ trợ Vue/React
Litepicker~10KBHỗ trợ Range selection cực tốt, không cần jQuery/Moment
Vanilla JS Datepicker~8KBTương thích Bootstrap 5, không phụ thuộc jQuery
[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:

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:

// 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

CriteriaMoment.jsDay.js
Bundle Size~231KB (gzipped with locales)~2KB (core) + plugin theo nhu cầu
MutabilityMutable (Dễ gây side-effects & bug)Immutable (An toàn tuyệt đối)
Tree-shakingKhông hỗ trợHỗ trợ từng plugin riêng lẻ
Kiến trúcMonolithicPlugin-based modular
Maintenance StatusMaintenance mode (Không thêm feature)Active Development
DateRangePicker SupportNative (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.