date-vir 8.5.1 → 8.6.0

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.
@@ -4,6 +4,7 @@ import { DateUnit, oneIndexedDateUnits } from '@date-vir/duration';
4
4
  import { checkValidShape, defineShape, exactShape, unionShape } from 'object-shape-tester';
5
5
  import { createFullDate } from '../full-date/create-full-date.js';
6
6
  import { toLuxonDateTime } from '../full-date/luxon-date-time-conversion.js';
7
+ import { utcTimezone } from '../timezone/timezones.js';
7
8
  import { calculateRelativeDate } from './calculate-relative-date.js';
8
9
  import { diffDates } from './diff-dates.js';
9
10
  /**
@@ -79,6 +80,24 @@ export function getStartDate(date, unit) {
79
80
  days: -1,
80
81
  });
81
82
  }
83
+ else if (unit === DateUnit.Day && date.timezone === utcTimezone) {
84
+ /**
85
+ * Fast path for UTC: UTC never observes DST, so midnight always exists and the start of the
86
+ * day is simply the same calendar date with all time fields zeroed. This avoids a
87
+ * comparatively expensive Luxon round-trip.
88
+ *
89
+ * Non-UTC zones fall through to the Luxon path below because a few zones have had DST
90
+ * transitions at midnight (e.g. `America/Asuncion` on 2020-10-04, where midnight did not
91
+ * exist and `startOf('day')` is 01:00, not 00:00).
92
+ */
93
+ return {
94
+ ...date,
95
+ hour: 0,
96
+ minute: 0,
97
+ second: 0,
98
+ millisecond: 0,
99
+ };
100
+ }
82
101
  else {
83
102
  return createFullDate(toLuxonDateTime(date).startOf(unit), date.timezone);
84
103
  }
@@ -1,4 +1,5 @@
1
1
  import { toTimestamp } from '../formatting/timestamp.js';
2
+ import { userTimezone } from '../timezone/timezones.js';
2
3
  /**
3
4
  * Convert a {@link FullDate} from a built-in JS
4
5
  * [`Date`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date).
@@ -24,5 +25,20 @@ import { toTimestamp } from '../formatting/timestamp.js';
24
25
  * ```
25
26
  */
26
27
  export function toJsDate(fullDate) {
28
+ /**
29
+ * Fast path: when the FullDate is already in the host environment's timezone, the JS `Date`
30
+ * local-time constructor produces the exact same instant as the Luxon round-trip (verified to
31
+ * agree even across DST gaps and overlaps) at a fraction of the cost. `userTimezone` is derived
32
+ * from the host environment, so it is the zone the `Date` constructor interprets its arguments
33
+ * in.
34
+ *
35
+ * The `year >= 100` guard avoids the `Date` constructor's legacy two-digit-year behavior (years
36
+ * 0-99 are mapped to 1900-1999); such dates fall through to the Luxon path, which handles them
37
+ * correctly.
38
+ */
39
+ if (fullDate.timezone === userTimezone && fullDate.year >= 100) {
40
+ return new Date(fullDate.year, fullDate.month - 1, // JS Date months are 0-indexed
41
+ fullDate.day, fullDate.hour, fullDate.minute, fullDate.second, fullDate.millisecond);
42
+ }
27
43
  return new Date(toTimestamp(fullDate));
28
44
  }
@@ -63,3 +63,47 @@ export declare const utcIsoStringShape: (defaultValue?: `${number}-${number}-${n
63
63
  * ```
64
64
  */
65
65
  export declare function isValidIsoString(input: unknown): input is UtcIsoString;
66
+ /**
67
+ * The abbreviated day-of-week names used by the RFC 1123 / HTTP-date format.
68
+ *
69
+ * @category Internal
70
+ */
71
+ export type HttpDateDayName = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | 'Sun';
72
+ /**
73
+ * The abbreviated month names used by the RFC 1123 / HTTP-date format.
74
+ *
75
+ * @category Internal
76
+ */
77
+ export type HttpDateMonthName = 'Jan' | 'Feb' | 'Mar' | 'Apr' | 'May' | 'Jun' | 'Jul' | 'Aug' | 'Sep' | 'Oct' | 'Nov' | 'Dec';
78
+ /**
79
+ * A full RFC 1123 / HTTP-date string in GMT: `Wed, 21 Oct 2015 07:28:00 GMT`. This is the format
80
+ * used by HTTP headers (per RFC 9110) and produced by `Date.prototype.toUTCString()`. It is the RFC
81
+ * 1123 counterpart to {@link UtcIsoString}.
82
+ *
83
+ * @category Formatting
84
+ * @see
85
+ * - https://www.rfc-editor.org/rfc/rfc9110#section-5.6.7
86
+ * - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
87
+ */
88
+ export type HttpDateString = `${HttpDateDayName}, ${number} ${HttpDateMonthName} ${number} ${JustTimeWithSecondsString} GMT`;
89
+ /**
90
+ * A shape definition for {@link HttpDateString}.
91
+ *
92
+ * @category Formatting
93
+ */
94
+ export declare const httpDateStringShape: (defaultValue?: `Mon, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} May ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} May ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} May ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} May ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} May ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} May ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} May ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Dec ${number} ${number}:${number}:${number} GMT` | undefined) => import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<`Mon, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} May ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Mon, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} May ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Tue, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} May ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Wed, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} May ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Thu, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} May ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Fri, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} May ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Sat, ${number} Dec ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jan ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Feb ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Mar ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Apr ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} May ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jun ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Jul ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Aug ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Sep ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Oct ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Nov ${number} ${number}:${number}:${number} GMT` | `Sun, ${number} Dec ${number} ${number}:${number}:${number} GMT`>>;
95
+ /**
96
+ * Checks if the input is a valid RFC 1123 / HTTP-date string and type guards the input.
97
+ *
98
+ * @category Formatting
99
+ * @category Assertion
100
+ * @example
101
+ *
102
+ * ```ts
103
+ * import {isValidHttpDateString} from 'date-vir';
104
+ *
105
+ * isValidHttpDateString('no'); // `false`
106
+ * isValidHttpDateString('Wed, 21 Oct 2015 07:28:00 GMT'); // `true`
107
+ * ```
108
+ */
109
+ export declare function isValidHttpDateString(input: unknown): input is HttpDateString;
@@ -30,3 +30,33 @@ export function isValidIsoString(input) {
30
30
  const datetime = DateTime.fromISO(input);
31
31
  return datetime.toUTC().toISO() === input;
32
32
  }
33
+ /**
34
+ * A shape definition for {@link HttpDateString}.
35
+ *
36
+ * @category Formatting
37
+ */
38
+ export const httpDateStringShape = createCustomShape({
39
+ default: new Date().toUTCString(),
40
+ name: 'HttpDateString',
41
+ checkValue(value) {
42
+ return isValidHttpDateString(value);
43
+ },
44
+ });
45
+ /**
46
+ * Checks if the input is a valid RFC 1123 / HTTP-date string and type guards the input.
47
+ *
48
+ * @category Formatting
49
+ * @category Assertion
50
+ * @example
51
+ *
52
+ * ```ts
53
+ * import {isValidHttpDateString} from 'date-vir';
54
+ *
55
+ * isValidHttpDateString('no'); // `false`
56
+ * isValidHttpDateString('Wed, 21 Oct 2015 07:28:00 GMT'); // `true`
57
+ * ```
58
+ */
59
+ export function isValidHttpDateString(input) {
60
+ const datetime = DateTime.fromHTTP(input);
61
+ return datetime.toUTC().toHTTP() === input;
62
+ }
@@ -1,5 +1,6 @@
1
+ import { type DateLike } from '../full-date/date-like.js';
1
2
  import { type FullDate } from '../full-date/full-date-shape.js';
2
- import { type UtcIsoString } from './string-format-types.js';
3
+ import { type HttpDateString, type UtcIsoString } from './string-format-types.js';
3
4
  /**
4
5
  * Convert a FullDate into a unix timestamp with milliseconds.
5
6
  *
@@ -25,7 +26,11 @@ import { type UtcIsoString } from './string-format-types.js';
25
26
  */
26
27
  export declare function toTimestamp(fullDate: FullDate): number;
27
28
  /**
28
- * Convert a FullDate into a UTC ISO string, always ending in Z.
29
+ * Convert a FullDate into a UTC ISO 8601 string, always ending in Z (for example
30
+ * `'2024-01-05T01:01:01.001Z'`). This is the machine-readable counterpart to
31
+ * {@link toHttpDateString}, which produces the human/HTTP-readable RFC 1123 format (`'Fri, 05 Jan
32
+ * 2024 01:01:01 GMT'`). Both represent the same instant in UTC; pick the one whose format your
33
+ * consumer expects.
29
34
  *
30
35
  * @category ISO
31
36
  * @category Formatting
@@ -47,5 +52,65 @@ export declare function toTimestamp(fullDate: FullDate): number;
47
52
  *
48
53
  * toUtcIsoString(exampleDate); // `'2024-01-05T01:01:01.001Z'`
49
54
  * ```
55
+ *
56
+ * @see {@link toHttpDateString} for the RFC 1123 / HTTP-date format.
50
57
  */
51
58
  export declare function toUtcIsoString(fullDate: FullDate): UtcIsoString;
59
+ /**
60
+ * Parse the given {@link DateLike} (interpreted in the given timezone) and convert it directly into
61
+ * a UTC ISO 8601 string, always ending in Z (for example `'2024-01-05T01:01:01.001Z'`). This is a
62
+ * shorthand for `toUtcIsoString(createFullDate(dateLike, timezone))`.
63
+ *
64
+ * @category ISO
65
+ * @category Formatting
66
+ * @example
67
+ *
68
+ * ```ts
69
+ * import {createUtcIsoString, utcTimezone} from 'date-vir';
70
+ *
71
+ * createUtcIsoString('June 1, 2024', utcTimezone);
72
+ * createUtcIsoString(new Date());
73
+ * ```
74
+ *
75
+ * @throws Error: if a valid date cannot be created.
76
+ * @see {@link toUtcIsoString} for converting an existing {@link FullDate}.
77
+ */
78
+ export declare function createUtcIsoString(
79
+ /** The original date representation to be converted into a UTC ISO string. */
80
+ dateLike: Readonly<DateLike>,
81
+ /** The timezone that this date/time is meant for / originated from. */
82
+ timezone?: string): UtcIsoString;
83
+ /**
84
+ * Convert a FullDate into an RFC 1123 / HTTP-date string in GMT (for example `'Fri, 05 Jan 2024
85
+ * 01:01:01 GMT'`). This is the format used by HTTP headers (per RFC 9110), and is identical to what
86
+ * `Date.prototype.toUTCString()` produces for the same instant.
87
+ *
88
+ * This is distinct from {@link toUtcIsoString}, which produces the ISO 8601 format
89
+ * (`'2024-01-05T01:01:01.001Z'`). Both represent the same instant in UTC, but they are not
90
+ * interchangeable: use this RFC 1123 form wherever an HTTP date is expected (e.g. `Date`,
91
+ * `Last-Modified`, `Expires`, or `Retry-After` headers), and use {@link toUtcIsoString} where ISO
92
+ * 8601 is expected. The output is always rendered in GMT regardless of the input's timezone.
93
+ *
94
+ * @category Formatting
95
+ * @example
96
+ *
97
+ * ```ts
98
+ * import {toHttpDateString} from 'date-vir';
99
+ *
100
+ * const exampleDate: FullDate = {
101
+ * year: 2024,
102
+ * month: 1,
103
+ * day: 5,
104
+ * hour: 1,
105
+ * minute: 1,
106
+ * second: 1,
107
+ * millisecond: 1,
108
+ * timezone: 'UTC',
109
+ * };
110
+ *
111
+ * toHttpDateString(exampleDate); // `'Fri, 05 Jan 2024 01:01:01 GMT'`
112
+ * ```
113
+ *
114
+ * @see {@link toUtcIsoString} for the ISO 8601 format.
115
+ */
116
+ export declare function toHttpDateString(fullDate: FullDate): HttpDateString;
@@ -1,5 +1,6 @@
1
+ import { createFullDate } from '../full-date/create-full-date.js';
1
2
  import { toLuxonDateTime } from '../full-date/luxon-date-time-conversion.js';
2
- import { utcTimezone } from '../timezone/timezones.js';
3
+ import { userTimezone, utcTimezone } from '../timezone/timezones.js';
3
4
  /**
4
5
  * Convert a FullDate into a unix timestamp with milliseconds.
5
6
  *
@@ -27,7 +28,11 @@ export function toTimestamp(fullDate) {
27
28
  return toLuxonDateTime(fullDate).toMillis();
28
29
  }
29
30
  /**
30
- * Convert a FullDate into a UTC ISO string, always ending in Z.
31
+ * Convert a FullDate into a UTC ISO 8601 string, always ending in Z (for example
32
+ * `'2024-01-05T01:01:01.001Z'`). This is the machine-readable counterpart to
33
+ * {@link toHttpDateString}, which produces the human/HTTP-readable RFC 1123 format (`'Fri, 05 Jan
34
+ * 2024 01:01:01 GMT'`). Both represent the same instant in UTC; pick the one whose format your
35
+ * consumer expects.
31
36
  *
32
37
  * @category ISO
33
38
  * @category Formatting
@@ -49,6 +54,8 @@ export function toTimestamp(fullDate) {
49
54
  *
50
55
  * toUtcIsoString(exampleDate); // `'2024-01-05T01:01:01.001Z'`
51
56
  * ```
57
+ *
58
+ * @see {@link toHttpDateString} for the RFC 1123 / HTTP-date format.
52
59
  */
53
60
  export function toUtcIsoString(fullDate) {
54
61
  const isoString = toLuxonDateTime(fullDate).setZone(utcTimezone).toISO();
@@ -59,3 +66,65 @@ export function toUtcIsoString(fullDate) {
59
66
  }
60
67
  return isoString;
61
68
  }
69
+ /**
70
+ * Parse the given {@link DateLike} (interpreted in the given timezone) and convert it directly into
71
+ * a UTC ISO 8601 string, always ending in Z (for example `'2024-01-05T01:01:01.001Z'`). This is a
72
+ * shorthand for `toUtcIsoString(createFullDate(dateLike, timezone))`.
73
+ *
74
+ * @category ISO
75
+ * @category Formatting
76
+ * @example
77
+ *
78
+ * ```ts
79
+ * import {createUtcIsoString, utcTimezone} from 'date-vir';
80
+ *
81
+ * createUtcIsoString('June 1, 2024', utcTimezone);
82
+ * createUtcIsoString(new Date());
83
+ * ```
84
+ *
85
+ * @throws Error: if a valid date cannot be created.
86
+ * @see {@link toUtcIsoString} for converting an existing {@link FullDate}.
87
+ */
88
+ export function createUtcIsoString(
89
+ /** The original date representation to be converted into a UTC ISO string. */
90
+ dateLike,
91
+ /** The timezone that this date/time is meant for / originated from. */
92
+ timezone = userTimezone) {
93
+ return toUtcIsoString(createFullDate(dateLike, timezone));
94
+ }
95
+ /**
96
+ * Convert a FullDate into an RFC 1123 / HTTP-date string in GMT (for example `'Fri, 05 Jan 2024
97
+ * 01:01:01 GMT'`). This is the format used by HTTP headers (per RFC 9110), and is identical to what
98
+ * `Date.prototype.toUTCString()` produces for the same instant.
99
+ *
100
+ * This is distinct from {@link toUtcIsoString}, which produces the ISO 8601 format
101
+ * (`'2024-01-05T01:01:01.001Z'`). Both represent the same instant in UTC, but they are not
102
+ * interchangeable: use this RFC 1123 form wherever an HTTP date is expected (e.g. `Date`,
103
+ * `Last-Modified`, `Expires`, or `Retry-After` headers), and use {@link toUtcIsoString} where ISO
104
+ * 8601 is expected. The output is always rendered in GMT regardless of the input's timezone.
105
+ *
106
+ * @category Formatting
107
+ * @example
108
+ *
109
+ * ```ts
110
+ * import {toHttpDateString} from 'date-vir';
111
+ *
112
+ * const exampleDate: FullDate = {
113
+ * year: 2024,
114
+ * month: 1,
115
+ * day: 5,
116
+ * hour: 1,
117
+ * minute: 1,
118
+ * second: 1,
119
+ * millisecond: 1,
120
+ * timezone: 'UTC',
121
+ * };
122
+ *
123
+ * toHttpDateString(exampleDate); // `'Fri, 05 Jan 2024 01:01:01 GMT'`
124
+ * ```
125
+ *
126
+ * @see {@link toUtcIsoString} for the ISO 8601 format.
127
+ */
128
+ export function toHttpDateString(fullDate) {
129
+ return toLuxonDateTime(fullDate).toHTTP();
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "date-vir",
3
- "version": "8.5.1",
3
+ "version": "8.6.0",
4
4
  "description": "Easy and explicit dates and times.",
5
5
  "keywords": [
6
6
  "date",
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "@augment-vir/assert": "^31.73.1",
38
38
  "@augment-vir/common": "^31.73.1",
39
- "@date-vir/duration": "^8.5.1",
39
+ "@date-vir/duration": "^8.6.0",
40
40
  "@types/luxon": "^3.7.1",
41
41
  "luxon": "^3.7.2",
42
42
  "object-shape-tester": "^6.14.0",