date-input.test.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { describe, expect, it } from "vitest";
  2. import { parseDateInputAsTimezone } from "@/lib/utils/date-input";
  3. describe("parseDateInputAsTimezone", () => {
  4. describe("date-only input (YYYY-MM-DD)", () => {
  5. it("should interpret date-only as end-of-day (23:59:59) in given timezone", () => {
  6. // Input: "2024-12-31" in Asia/Shanghai (UTC+8)
  7. // Expected: 2024-12-31 23:59:59 in Shanghai = 2024-12-31 15:59:59 UTC
  8. const result = parseDateInputAsTimezone("2024-12-31", "Asia/Shanghai");
  9. expect(result.getUTCFullYear()).toBe(2024);
  10. expect(result.getUTCMonth()).toBe(11); // December = 11
  11. expect(result.getUTCDate()).toBe(31);
  12. expect(result.getUTCHours()).toBe(15); // 23:59:59 Shanghai = 15:59:59 UTC
  13. expect(result.getUTCMinutes()).toBe(59);
  14. expect(result.getUTCSeconds()).toBe(59);
  15. });
  16. it("should handle UTC timezone correctly", () => {
  17. // Input: "2024-06-15" in UTC
  18. // Expected: 2024-06-15 23:59:59 UTC
  19. const result = parseDateInputAsTimezone("2024-06-15", "UTC");
  20. expect(result.getUTCFullYear()).toBe(2024);
  21. expect(result.getUTCMonth()).toBe(5); // June = 5
  22. expect(result.getUTCDate()).toBe(15);
  23. expect(result.getUTCHours()).toBe(23);
  24. expect(result.getUTCMinutes()).toBe(59);
  25. expect(result.getUTCSeconds()).toBe(59);
  26. });
  27. it("should handle negative offset timezone (America/New_York)", () => {
  28. // Input: "2024-07-04" in America/New_York (UTC-4 during DST)
  29. // Expected: 2024-07-04 23:59:59 in NY = 2024-07-05 03:59:59 UTC
  30. const result = parseDateInputAsTimezone("2024-07-04", "America/New_York");
  31. expect(result.getUTCFullYear()).toBe(2024);
  32. expect(result.getUTCMonth()).toBe(6); // July = 6
  33. expect(result.getUTCDate()).toBe(5); // Next day in UTC
  34. expect(result.getUTCHours()).toBe(3); // 23:59:59 NY (UTC-4) = 03:59:59 UTC next day
  35. expect(result.getUTCMinutes()).toBe(59);
  36. expect(result.getUTCSeconds()).toBe(59);
  37. });
  38. it("should handle date at year boundary", () => {
  39. // Input: "2024-01-01" in Asia/Tokyo (UTC+9)
  40. // Expected: 2024-01-01 23:59:59 in Tokyo = 2024-01-01 14:59:59 UTC
  41. const result = parseDateInputAsTimezone("2024-01-01", "Asia/Tokyo");
  42. expect(result.getUTCFullYear()).toBe(2024);
  43. expect(result.getUTCMonth()).toBe(0); // January = 0
  44. expect(result.getUTCDate()).toBe(1);
  45. expect(result.getUTCHours()).toBe(14); // 23:59:59 Tokyo (UTC+9) = 14:59:59 UTC
  46. });
  47. });
  48. describe("ISO datetime input", () => {
  49. it("should handle ISO datetime string", () => {
  50. // Input: "2024-12-31T10:30:00" in Asia/Shanghai
  51. // Expected: 2024-12-31 10:30:00 in Shanghai = 2024-12-31 02:30:00 UTC
  52. const result = parseDateInputAsTimezone("2024-12-31T10:30:00", "Asia/Shanghai");
  53. expect(result.getUTCFullYear()).toBe(2024);
  54. expect(result.getUTCMonth()).toBe(11);
  55. expect(result.getUTCDate()).toBe(31);
  56. expect(result.getUTCHours()).toBe(2); // 10:30 Shanghai = 02:30 UTC
  57. expect(result.getUTCMinutes()).toBe(30);
  58. });
  59. it("should handle ISO datetime with Z suffix - note: behavior depends on runtime TZ", () => {
  60. // NOTE: Z-suffixed input is not a typical use case for this function.
  61. // User input from date pickers typically doesn't include Z suffix.
  62. // When Z suffix is present, new Date() parses it as UTC, but fromZonedTime
  63. // reads the LOCAL time components (which depend on runtime timezone).
  64. //
  65. // For this reason, we recommend NOT using Z-suffixed input with this function.
  66. // This test documents the behavior for awareness, not for correctness assertion.
  67. const result = parseDateInputAsTimezone("2024-12-31T10:30:00Z", "Asia/Shanghai");
  68. // Just verify it doesn't throw and returns a valid date
  69. expect(result).toBeInstanceOf(Date);
  70. expect(Number.isNaN(result.getTime())).toBe(false);
  71. });
  72. });
  73. describe("error handling", () => {
  74. it("should throw for invalid date string", () => {
  75. expect(() => parseDateInputAsTimezone("invalid-date", "UTC")).toThrow(
  76. "Invalid date input: invalid-date"
  77. );
  78. });
  79. it("should throw for empty string", () => {
  80. expect(() => parseDateInputAsTimezone("", "UTC")).toThrow();
  81. });
  82. });
  83. describe("DST edge cases", () => {
  84. it("should handle DST transition date in spring (America/New_York)", () => {
  85. // March 10, 2024 is when DST starts in US (clocks spring forward at 2am)
  86. // Input: "2024-03-10" in America/New_York
  87. // Expected: 2024-03-10 23:59:59 in NY (UTC-4 after DST) = 2024-03-11 03:59:59 UTC
  88. const result = parseDateInputAsTimezone("2024-03-10", "America/New_York");
  89. expect(result.getUTCFullYear()).toBe(2024);
  90. expect(result.getUTCMonth()).toBe(2); // March = 2
  91. expect(result.getUTCDate()).toBe(11); // Next day in UTC
  92. expect(result.getUTCHours()).toBe(3); // UTC-4 offset after DST
  93. });
  94. it("should handle DST transition date in fall (America/New_York)", () => {
  95. // November 3, 2024 is when DST ends in US (clocks fall back at 2am)
  96. // Input: "2024-11-03" in America/New_York
  97. // Expected: 2024-11-03 23:59:59 in NY (UTC-5 after DST ends) = 2024-11-04 04:59:59 UTC
  98. const result = parseDateInputAsTimezone("2024-11-03", "America/New_York");
  99. expect(result.getUTCFullYear()).toBe(2024);
  100. expect(result.getUTCMonth()).toBe(10); // November = 10
  101. expect(result.getUTCDate()).toBe(4); // Next day in UTC
  102. expect(result.getUTCHours()).toBe(4); // UTC-5 offset after DST ends
  103. });
  104. });
  105. });