@rvoh/dream 2.3.0-alpha.4 → 2.3.0-alpha.6

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.
Files changed (194) hide show
  1. package/dist/cjs/src/utils/datetime/CalendarDate.js +32 -0
  2. package/dist/cjs/src/utils/datetime/DateTime.js +112 -61
  3. package/dist/cjs/src/utils/datetime/helpers/isoTimeDecimalString.js +8 -4
  4. package/dist/cjs/src/utils/datetime/helpers/replaceISOMicroseconds.js +4 -1
  5. package/dist/esm/src/utils/datetime/CalendarDate.js +32 -0
  6. package/dist/esm/src/utils/datetime/DateTime.js +112 -61
  7. package/dist/esm/src/utils/datetime/helpers/isoTimeDecimalString.js +8 -4
  8. package/dist/esm/src/utils/datetime/helpers/replaceISOMicroseconds.js +4 -1
  9. package/dist/types/src/types/datetime.d.ts +2 -0
  10. package/dist/types/src/types/datetime.ts +2 -0
  11. package/dist/types/src/utils/datetime/CalendarDate.d.ts +19 -0
  12. package/dist/types/src/utils/datetime/DateTime.d.ts +46 -11
  13. package/dist/types/src/utils/datetime/helpers/isoTimeDecimalString.d.ts +3 -2
  14. package/dist/types/src/utils/datetime/helpers/replaceISOMicroseconds.d.ts +4 -2
  15. package/docs/assets/search.js +1 -1
  16. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  17. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  18. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  19. package/docs/classes/db.QueryDriverBase.html +31 -31
  20. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  21. package/docs/classes/errors.ColumnOverflow.html +3 -3
  22. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  23. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  24. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  25. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  26. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  27. package/docs/classes/errors.InvalidDateTime.html +2 -2
  28. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  29. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  30. package/docs/classes/errors.NotNullViolation.html +3 -3
  31. package/docs/classes/errors.RecordNotFound.html +3 -3
  32. package/docs/classes/errors.ValidationError.html +3 -3
  33. package/docs/classes/index.CalendarDate.html +56 -45
  34. package/docs/classes/index.DateTime.html +165 -145
  35. package/docs/classes/index.Decorators.html +19 -19
  36. package/docs/classes/index.Dream.html +116 -116
  37. package/docs/classes/index.DreamApp.html +5 -5
  38. package/docs/classes/index.DreamTransaction.html +2 -2
  39. package/docs/classes/index.Env.html +2 -2
  40. package/docs/classes/index.Query.html +56 -56
  41. package/docs/classes/system.CliFileWriter.html +2 -2
  42. package/docs/classes/system.DreamBin.html +2 -2
  43. package/docs/classes/system.DreamCLI.html +5 -5
  44. package/docs/classes/system.DreamImporter.html +2 -2
  45. package/docs/classes/system.DreamLogos.html +2 -2
  46. package/docs/classes/system.DreamSerializerBuilder.html +8 -8
  47. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  48. package/docs/classes/system.PathHelpers.html +3 -3
  49. package/docs/classes/utils.Encrypt.html +2 -2
  50. package/docs/classes/utils.Range.html +2 -2
  51. package/docs/functions/db.closeAllDbConnections.html +1 -1
  52. package/docs/functions/db.dreamDbConnections.html +1 -1
  53. package/docs/functions/db.untypedDb.html +1 -1
  54. package/docs/functions/db.validateColumn.html +1 -1
  55. package/docs/functions/db.validateTable.html +1 -1
  56. package/docs/functions/errors.pgErrorType.html +1 -1
  57. package/docs/functions/index.DreamSerializer.html +1 -1
  58. package/docs/functions/index.ObjectSerializer.html +1 -1
  59. package/docs/functions/index.ReplicaSafe.html +1 -1
  60. package/docs/functions/index.STI.html +1 -1
  61. package/docs/functions/index.SoftDelete.html +1 -1
  62. package/docs/functions/utils.camelize.html +1 -1
  63. package/docs/functions/utils.capitalize.html +1 -1
  64. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  65. package/docs/functions/utils.compact.html +1 -1
  66. package/docs/functions/utils.groupBy.html +1 -1
  67. package/docs/functions/utils.hyphenize.html +1 -1
  68. package/docs/functions/utils.intersection.html +1 -1
  69. package/docs/functions/utils.isEmpty.html +1 -1
  70. package/docs/functions/utils.normalizeUnicode.html +1 -1
  71. package/docs/functions/utils.pascalize.html +1 -1
  72. package/docs/functions/utils.percent.html +1 -1
  73. package/docs/functions/utils.range-1.html +1 -1
  74. package/docs/functions/utils.round.html +1 -1
  75. package/docs/functions/utils.sanitizeString.html +1 -1
  76. package/docs/functions/utils.snakeify.html +1 -1
  77. package/docs/functions/utils.sort.html +1 -1
  78. package/docs/functions/utils.sortBy.html +1 -1
  79. package/docs/functions/utils.sortObjectByKey.html +1 -1
  80. package/docs/functions/utils.sortObjectByValue.html +1 -1
  81. package/docs/functions/utils.uncapitalize.html +1 -1
  82. package/docs/functions/utils.uniq.html +1 -1
  83. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  84. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  85. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  86. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  87. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  88. package/docs/interfaces/types.DecoratorContext.html +2 -2
  89. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  90. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  91. package/docs/interfaces/types.DurationObject.html +2 -2
  92. package/docs/interfaces/types.EncryptOptions.html +2 -2
  93. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  94. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  95. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  96. package/docs/modules/db.html +1 -1
  97. package/docs/modules/errors.html +1 -1
  98. package/docs/modules/index.html +1 -1
  99. package/docs/modules/openapi.html +1 -1
  100. package/docs/modules/system.html +1 -1
  101. package/docs/modules/types.html +1 -1
  102. package/docs/modules/utils.html +1 -1
  103. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  104. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  105. package/docs/types/openapi.OpenapiFormats.html +1 -1
  106. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  107. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  108. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  109. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  110. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  111. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  112. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  113. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  114. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  115. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  116. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  117. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  118. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +1 -1
  119. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  120. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  121. package/docs/types/openapi.OpenapiSchemaNull.html +1 -1
  122. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  123. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  124. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  125. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  126. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  127. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  128. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  130. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  131. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  132. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  133. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  134. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  135. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  136. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  137. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  138. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  139. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  141. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  142. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  143. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  144. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  145. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  146. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  147. package/docs/types/types.Camelized.html +1 -1
  148. package/docs/types/types.DbConnectionType.html +1 -1
  149. package/docs/types/types.DbTypes.html +1 -1
  150. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  151. package/docs/types/types.DreamAttributes.html +1 -1
  152. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  153. package/docs/types/types.DreamClassColumn.html +1 -1
  154. package/docs/types/types.DreamColumn.html +1 -1
  155. package/docs/types/types.DreamColumnNames.html +1 -1
  156. package/docs/types/types.DreamLogLevel.html +1 -1
  157. package/docs/types/types.DreamLogger.html +1 -1
  158. package/docs/types/types.DreamModelSerializerType.html +1 -1
  159. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  160. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  161. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  162. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  163. package/docs/types/types.DreamSerializable.html +1 -1
  164. package/docs/types/types.DreamSerializableArray.html +1 -1
  165. package/docs/types/types.DreamSerializerKey.html +1 -1
  166. package/docs/types/types.DreamSerializers.html +1 -1
  167. package/docs/types/types.DreamVirtualColumns.html +1 -1
  168. package/docs/types/types.DurationUnit.html +1 -1
  169. package/docs/types/types.EncryptAlgorithm.html +1 -1
  170. package/docs/types/types.HasManyStatement.html +1 -1
  171. package/docs/types/types.HasOneStatement.html +1 -1
  172. package/docs/types/types.Hyphenized.html +1 -1
  173. package/docs/types/types.Pascalized.html +1 -1
  174. package/docs/types/types.PrimaryKeyType.html +1 -1
  175. package/docs/types/types.RoundingPrecision.html +1 -1
  176. package/docs/types/types.SerializerCasing.html +1 -1
  177. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  178. package/docs/types/types.Snakeified.html +1 -1
  179. package/docs/types/types.StrictInterface.html +1 -1
  180. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  181. package/docs/types/types.UpdateableProperties.html +1 -1
  182. package/docs/types/types.ValidationType.html +1 -1
  183. package/docs/types/types.ViewModel.html +1 -1
  184. package/docs/types/types.ViewModelClass.html +1 -1
  185. package/docs/types/types.WeekdayName.html +1 -1
  186. package/docs/types/types.WhereStatementForDream.html +1 -1
  187. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  188. package/docs/variables/index.DreamConst.html +1 -1
  189. package/docs/variables/index.ops.html +1 -1
  190. package/docs/variables/openapi.openapiPrimitiveTypes-1.html +1 -1
  191. package/docs/variables/openapi.openapiShorthandPrimitiveTypes-1.html +1 -1
  192. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  193. package/docs/variables/system.primaryKeyTypes.html +1 -1
  194. package/package.json +1 -1
@@ -107,6 +107,38 @@ export default class CalendarDate {
107
107
  }
108
108
  return new CalendarDate(dateTime);
109
109
  }
110
+ /**
111
+ * Create a CalendarDate from a custom format string.
112
+ * Uses Luxon format tokens (e.g., 'MM/dd/yyyy', 'MMMM dd, yyyy').
113
+ * @param text - The string to parse
114
+ * @param format - Format string using Luxon tokens
115
+ * @param options - Optional zone and locale options
116
+ * @returns A CalendarDate for the parsed date
117
+ * @throws {InvalidCalendarDate} When the string doesn't match the format or is invalid
118
+ * @example
119
+ * ```ts
120
+ * CalendarDate.fromFormat('12/15/2017', 'MM/dd/yyyy')
121
+ * CalendarDate.fromFormat('May 25, 1982', 'MMMM dd, yyyy')
122
+ * CalendarDate.fromFormat('mai 25, 1982', 'MMMM dd, yyyy', { locale: 'fr' })
123
+ * ```
124
+ */
125
+ static fromFormat(text, format, { zone, locale } = {}) {
126
+ let dateTime;
127
+ try {
128
+ const opts = {};
129
+ if (zone)
130
+ opts.zone = zone;
131
+ if (locale)
132
+ opts.locale = locale;
133
+ dateTime = DateTime.fromFormat(text, format, opts);
134
+ }
135
+ catch (error) {
136
+ if (error instanceof Error)
137
+ throw new InvalidCalendarDate(error);
138
+ throw error;
139
+ }
140
+ return new CalendarDate(dateTime);
141
+ }
110
142
  /**
111
143
  * Create a CalendarDate from an object with date units.
112
144
  * @param obj - Object with year, month, day properties
@@ -381,17 +381,20 @@ export class DateTime {
381
381
  }
382
382
  /**
383
383
  * Create a DateTime from epoch milliseconds.
384
- * @param milliseconds - Unix timestamp in milliseconds
384
+ * @param millisecondInput - Unix timestamp in milliseconds (fractional part becomes microseconds)
385
385
  * @param options - Optional zone/locale options
386
386
  * @returns A DateTime for the given instant
387
387
  * @example
388
388
  * ```ts
389
389
  * DateTime.fromMillis(1707234567890)
390
+ * DateTime.fromMillis(1707234567890.123) // .123 ms = 123 microseconds
390
391
  * ```
391
392
  */
392
- static fromMillis(milliseconds, options) {
393
- const luxonDatetime = LuxonDateTime.fromMillis(milliseconds, options);
394
- return new DateTime(luxonDatetime, 0);
393
+ static fromMillis(millisecondInput, options) {
394
+ const { milliseconds, microseconds } = microsecondParts(millisecondInput * 1000, {
395
+ errorIfNegative: false,
396
+ });
397
+ return new DateTime(LuxonDateTime.fromMillis(milliseconds, options), microseconds);
395
398
  }
396
399
  /**
397
400
  * Create a DateTime from epoch microseconds.
@@ -410,17 +413,22 @@ export class DateTime {
410
413
  }
411
414
  /**
412
415
  * Create a DateTime from epoch seconds.
413
- * @param seconds - Unix timestamp in seconds
416
+ * Fractional seconds are converted to milliseconds and microseconds.
417
+ * @param seconds - Unix timestamp in seconds (fractional part becomes ms + µs)
414
418
  * @param options - Optional zone/locale options
415
419
  * @returns A DateTime for the given instant
416
420
  * @example
417
421
  * ```ts
418
422
  * DateTime.fromSeconds(1707234567)
423
+ * DateTime.fromSeconds(1707234567.123456) // .123456 seconds = 123ms + 456µs
419
424
  * ```
420
425
  */
421
426
  static fromSeconds(seconds, options) {
422
- const luxonDatetime = LuxonDateTime.fromSeconds(seconds, options);
423
- return new DateTime(luxonDatetime, 0);
427
+ // Convert seconds to microseconds to preserve full precision
428
+ const totalMicroseconds = seconds * 1_000_000;
429
+ const { milliseconds, microseconds } = microsecondParts(totalMicroseconds, { errorIfNegative: false });
430
+ const luxonDatetime = LuxonDateTime.fromMillis(milliseconds, options);
431
+ return new DateTime(luxonDatetime, microseconds);
424
432
  }
425
433
  /**
426
434
  * Create a DateTime from an object with date/time units.
@@ -481,6 +489,46 @@ export class DateTime {
481
489
  const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromSQL(textForLuxon, opts));
482
490
  return new DateTime(luxonDatetime, microsecond);
483
491
  }
492
+ /**
493
+ * Create a DateTime from a custom format string.
494
+ * Supports standard Luxon format tokens plus 'u' or 'SSSSSS' for microseconds (6 decimal places).
495
+ * @param text - The string to parse
496
+ * @param format - Format string using Luxon tokens (e.g., 'MM/dd/yyyy HH:mm:ss.u')
497
+ * @param opts - Optional parsing options (zone, locale, etc.)
498
+ * @returns A DateTime for the parsed instant
499
+ * @throws {InvalidDateTime} When the string doesn't match the format or is invalid
500
+ * @example
501
+ * ```ts
502
+ * DateTime.fromFormat('12/15/2017', 'MM/dd/yyyy')
503
+ * DateTime.fromFormat('12/15/2017 10:30:45', 'MM/dd/yyyy HH:mm:ss')
504
+ * DateTime.fromFormat('12/15/2017 10:30:45.123456', 'MM/dd/yyyy HH:mm:ss.u')
505
+ * DateTime.fromFormat('12/15/2017 10:30:45.123456', 'MM/dd/yyyy HH:mm:ss.SSSSSS')
506
+ * DateTime.fromFormat('mai 25, 1982', 'MMMM dd, yyyy', { locale: 'fr' })
507
+ * ```
508
+ */
509
+ static fromFormat(text, format, opts) {
510
+ // Check if format includes microsecond token ('u' or 'SSSSSS')
511
+ const hasMicrosecondToken = format.includes('.u') || format.includes('.SSSSSS');
512
+ let microsecond = 0;
513
+ if (hasMicrosecondToken) {
514
+ // Extract microseconds from the text
515
+ const { microsecond: extractedMicrosecond } = parseFractionalPart(text);
516
+ microsecond = extractedMicrosecond;
517
+ // Replace microsecond token with millisecond token for Luxon
518
+ // 'u' -> 'SSS' (Luxon only supports milliseconds)
519
+ // 'SSSSSS' -> 'SSS'
520
+ const formatForLuxon = format.replace(/\.u\b/, '.SSS').replace(/\.SSSSSS\b/, '.SSS');
521
+ // Truncate fractional part to 3 digits for Luxon
522
+ const textForLuxon = toThreeDecimalFraction(text);
523
+ const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromFormat(textForLuxon, formatForLuxon, opts));
524
+ return new DateTime(luxonDatetime, microsecond);
525
+ }
526
+ else {
527
+ // No microsecond token, use Luxon directly
528
+ const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromFormat(text, format, opts));
529
+ return new DateTime(luxonDatetime, 0);
530
+ }
531
+ }
484
532
  /**
485
533
  * Returns an ISO 8601 string with 6 fractional second digits (milliseconds + microseconds).
486
534
  * @param opts - Optional format options (includeOffset, suppressMilliseconds, etc.)
@@ -813,20 +861,38 @@ export class DateTime {
813
861
  return luxonSeconds + additionalMicroseconds;
814
862
  }
815
863
  /**
816
- * Returns the epoch time in seconds as an integer (floor of toSeconds).
817
- * Truncates any fractional seconds, returning only the whole seconds portion.
818
- * Equivalent to Math.floor(toSeconds()).
819
- * @returns Unix timestamp in seconds (integer only, no fractional part)
864
+ * Returns the epoch time in seconds as an integer, truncating any fractional part.
820
865
  * @example
821
866
  * ```ts
822
- * DateTime.fromSeconds(1707234567).toUnixInteger() // 1707234567
823
- * DateTime.fromISO('2026-02-07T09:03:44.123456Z').toUnixInteger() // 1770455024 (fractional part truncated)
824
- * DateTime.fromISO('2026-02-07T09:03:44.999999Z').toUnixInteger() // 1770455024 (not rounded up)
867
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerSeconds // 1770455024
868
+ * DateTime.fromISO('2026-02-07T09:03:44.999999Z').unixIntegerSeconds // 1770455024 (not rounded up)
825
869
  * ```
826
870
  */
827
- toUnixInteger() {
871
+ get unixIntegerSeconds() {
828
872
  return Math.floor(this.toSeconds());
829
873
  }
874
+ /**
875
+ * Returns the epoch time in milliseconds as an integer, truncating any fractional part.
876
+ * @example
877
+ * ```ts
878
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerMilliseconds // 1770455024123
879
+ * DateTime.fromISO('2026-02-07T09:03:44.123999Z').unixIntegerMilliseconds // 1770455024123 (not rounded up)
880
+ * ```
881
+ */
882
+ get unixIntegerMilliseconds() {
883
+ return Math.floor(this.toMillis());
884
+ }
885
+ /**
886
+ * Returns the epoch time in microseconds as an integer.
887
+ * Equivalent to `toMicroseconds()` since microseconds are always whole numbers.
888
+ * @example
889
+ * ```ts
890
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerMicroseconds // 1770455024123456
891
+ * ```
892
+ */
893
+ get unixIntegerMicroseconds() {
894
+ return this.toMicroseconds();
895
+ }
830
896
  /**
831
897
  * Returns the earliest DateTime from the given arguments.
832
898
  * @param dateTimes - DateTimes to compare
@@ -985,58 +1051,43 @@ export class DateTime {
985
1051
  * ```
986
1052
  */
987
1053
  diff(other, unit) {
988
- // Check if we need to calculate microseconds
989
- const needsMicroseconds = unit === 'microseconds' ||
990
- (Array.isArray(unit) && unit.includes('microseconds'));
991
- // Check if we also need milliseconds
992
- const needsMilliseconds = unit === 'milliseconds' ||
993
- (Array.isArray(unit) && unit.includes('milliseconds'));
994
- // Filter out 'microseconds' from the unit array since Luxon doesn't support it
995
- let luxonUnits = unit;
996
- if (Array.isArray(unit)) {
997
- const filtered = unit.filter(u => u !== 'microseconds');
998
- luxonUnits = filtered.length > 0 ? filtered : undefined;
999
- }
1000
- else if (unit === 'microseconds') {
1001
- luxonUnits = undefined;
1002
- }
1003
- // Get Luxon's diff (which handles all units except microseconds)
1004
- const luxonDuration = this.luxonDatetime.diff(other.toLuxon(), luxonUnits);
1005
- const fullObject = luxonDuration.toObject();
1006
- // Calculate microsecond difference if needed
1007
- if (needsMicroseconds || unit === undefined) {
1008
- // When microseconds is the ONLY unit requested, return total microseconds
1054
+ const unitArray = unit === undefined ? undefined : typeof unit === 'string' ? [unit] : unit;
1055
+ const needsMicroseconds = !unitArray || unitArray.includes('microseconds');
1056
+ const needsMilliseconds = !unitArray || unitArray.includes('milliseconds');
1057
+ // Strip 'microseconds' before passing to Luxon (it doesn't support it)
1058
+ const luxonUnits = unitArray?.filter(u => u !== 'microseconds');
1059
+ const luxonDuration = this.luxonDatetime.diff(other.toLuxon(), luxonUnits && luxonUnits.length > 0 ? luxonUnits : undefined);
1060
+ const result = luxonDuration.toObject();
1061
+ if (needsMicroseconds) {
1009
1062
  if (unit === 'microseconds') {
1010
- const thisTotalMicroseconds = this.toMicroseconds();
1011
- const otherTotalMicroseconds = other.toMicroseconds();
1012
- fullObject.microseconds = thisTotalMicroseconds - otherTotalMicroseconds;
1063
+ // Only microseconds requested: return total difference
1064
+ result.microseconds = this.toMicroseconds() - other.toMicroseconds();
1013
1065
  }
1014
- else {
1015
- // When microseconds is requested with other units, return only the fractional part (0-999)
1016
- const thisMicrosecond = this.microsecond;
1017
- const otherMicrosecond = other.microsecond;
1018
- fullObject.microseconds = thisMicrosecond - otherMicrosecond;
1066
+ else if (needsMilliseconds && result.milliseconds !== undefined) {
1067
+ // Both ms + µs requested: combine Luxon's ms with µs field diff, then re-split.
1068
+ // Example: Luxon says -1ms, µs fields are 999-0=+999 → total=-1µs → 0ms, -1µs
1069
+ const totalMicroDiff = Math.round(result.milliseconds * 1000) + (this.microsecond - other.microsecond);
1070
+ result.milliseconds = Math.trunc(totalMicroDiff / 1000) || 0; // || 0 normalizes -0
1071
+ result.microseconds = totalMicroDiff - result.milliseconds * 1000;
1019
1072
  }
1020
- // If milliseconds are also requested/present, truncate them to whole numbers
1021
- // since the fractional part is now represented in microseconds
1022
- if ((needsMilliseconds || unit === undefined) && fullObject.milliseconds !== undefined) {
1023
- fullObject.milliseconds = Math.trunc(fullObject.milliseconds);
1073
+ else {
1074
+ // µs without ms: just the field difference
1075
+ result.microseconds = this.microsecond - other.microsecond;
1024
1076
  }
1025
1077
  }
1026
- // If no unit specified, return all units (including microseconds)
1027
- if (unit === undefined) {
1028
- return fullObject;
1029
- }
1030
- // If unit is a single string, return only that unit
1031
- if (typeof unit === 'string') {
1032
- return { [unit]: fullObject[unit] ?? 0 };
1033
- }
1034
- // If unit is an array, return only those units
1035
- const result = {};
1036
- for (const u of unit) {
1037
- result[u] = fullObject[u] ?? 0;
1078
+ else if (needsMilliseconds && result.milliseconds !== undefined) {
1079
+ // ms without µs: truncate fractional part
1080
+ result.milliseconds = Math.trunc(result.milliseconds);
1038
1081
  }
1039
- return result;
1082
+ // Return only the requested units
1083
+ if (unit === undefined)
1084
+ return result;
1085
+ if (typeof unit === 'string')
1086
+ return { [unit]: result[unit] ?? 0 };
1087
+ const filtered = {};
1088
+ for (const u of unit)
1089
+ filtered[u] = result[u] ?? 0;
1090
+ return filtered;
1040
1091
  }
1041
1092
  /**
1042
1093
  * Returns the difference between this DateTime and now.
@@ -1,8 +1,12 @@
1
- export default function isoTimeDecimalString(datetime, { nullIfZero }) {
1
+ export default function isoTimeDecimalString(datetime, { nullIfZero, truncateMicroseconds }) {
2
2
  const milliseconds = datetime.millisecond;
3
- const microseconds = datetime.microsecond;
4
- const totalMicroseconds = milliseconds * 1000 + microseconds;
5
- if (totalMicroseconds === 0 && nullIfZero)
3
+ if (truncateMicroseconds) {
4
+ if (nullIfZero && milliseconds === 0)
5
+ return null;
6
+ return milliseconds.toString().padStart(3, '0');
7
+ }
8
+ const totalMicroseconds = milliseconds * 1000 + datetime.microsecond;
9
+ if (nullIfZero && totalMicroseconds === 0)
6
10
  return null;
7
11
  return totalMicroseconds.toString().padStart(6, '0');
8
12
  }
@@ -1,6 +1,9 @@
1
1
  import isoTimeDecimalString from './isoTimeDecimalString.js';
2
2
  export default function replaceISOMicroseconds(timeObj, isoString, opts) {
3
- const decimalString = isoTimeDecimalString(timeObj, { nullIfZero: opts?.suppressMilliseconds });
3
+ const decimalString = isoTimeDecimalString(timeObj, {
4
+ nullIfZero: opts?.suppressMilliseconds,
5
+ truncateMicroseconds: opts?.truncateMicroseconds,
6
+ });
4
7
  if (decimalString === null)
5
8
  return isoString;
6
9
  // Match time in both ISO format (with T) and SQL format (with space)
@@ -107,6 +107,38 @@ export default class CalendarDate {
107
107
  }
108
108
  return new CalendarDate(dateTime);
109
109
  }
110
+ /**
111
+ * Create a CalendarDate from a custom format string.
112
+ * Uses Luxon format tokens (e.g., 'MM/dd/yyyy', 'MMMM dd, yyyy').
113
+ * @param text - The string to parse
114
+ * @param format - Format string using Luxon tokens
115
+ * @param options - Optional zone and locale options
116
+ * @returns A CalendarDate for the parsed date
117
+ * @throws {InvalidCalendarDate} When the string doesn't match the format or is invalid
118
+ * @example
119
+ * ```ts
120
+ * CalendarDate.fromFormat('12/15/2017', 'MM/dd/yyyy')
121
+ * CalendarDate.fromFormat('May 25, 1982', 'MMMM dd, yyyy')
122
+ * CalendarDate.fromFormat('mai 25, 1982', 'MMMM dd, yyyy', { locale: 'fr' })
123
+ * ```
124
+ */
125
+ static fromFormat(text, format, { zone, locale } = {}) {
126
+ let dateTime;
127
+ try {
128
+ const opts = {};
129
+ if (zone)
130
+ opts.zone = zone;
131
+ if (locale)
132
+ opts.locale = locale;
133
+ dateTime = DateTime.fromFormat(text, format, opts);
134
+ }
135
+ catch (error) {
136
+ if (error instanceof Error)
137
+ throw new InvalidCalendarDate(error);
138
+ throw error;
139
+ }
140
+ return new CalendarDate(dateTime);
141
+ }
110
142
  /**
111
143
  * Create a CalendarDate from an object with date units.
112
144
  * @param obj - Object with year, month, day properties
@@ -381,17 +381,20 @@ export class DateTime {
381
381
  }
382
382
  /**
383
383
  * Create a DateTime from epoch milliseconds.
384
- * @param milliseconds - Unix timestamp in milliseconds
384
+ * @param millisecondInput - Unix timestamp in milliseconds (fractional part becomes microseconds)
385
385
  * @param options - Optional zone/locale options
386
386
  * @returns A DateTime for the given instant
387
387
  * @example
388
388
  * ```ts
389
389
  * DateTime.fromMillis(1707234567890)
390
+ * DateTime.fromMillis(1707234567890.123) // .123 ms = 123 microseconds
390
391
  * ```
391
392
  */
392
- static fromMillis(milliseconds, options) {
393
- const luxonDatetime = LuxonDateTime.fromMillis(milliseconds, options);
394
- return new DateTime(luxonDatetime, 0);
393
+ static fromMillis(millisecondInput, options) {
394
+ const { milliseconds, microseconds } = microsecondParts(millisecondInput * 1000, {
395
+ errorIfNegative: false,
396
+ });
397
+ return new DateTime(LuxonDateTime.fromMillis(milliseconds, options), microseconds);
395
398
  }
396
399
  /**
397
400
  * Create a DateTime from epoch microseconds.
@@ -410,17 +413,22 @@ export class DateTime {
410
413
  }
411
414
  /**
412
415
  * Create a DateTime from epoch seconds.
413
- * @param seconds - Unix timestamp in seconds
416
+ * Fractional seconds are converted to milliseconds and microseconds.
417
+ * @param seconds - Unix timestamp in seconds (fractional part becomes ms + µs)
414
418
  * @param options - Optional zone/locale options
415
419
  * @returns A DateTime for the given instant
416
420
  * @example
417
421
  * ```ts
418
422
  * DateTime.fromSeconds(1707234567)
423
+ * DateTime.fromSeconds(1707234567.123456) // .123456 seconds = 123ms + 456µs
419
424
  * ```
420
425
  */
421
426
  static fromSeconds(seconds, options) {
422
- const luxonDatetime = LuxonDateTime.fromSeconds(seconds, options);
423
- return new DateTime(luxonDatetime, 0);
427
+ // Convert seconds to microseconds to preserve full precision
428
+ const totalMicroseconds = seconds * 1_000_000;
429
+ const { milliseconds, microseconds } = microsecondParts(totalMicroseconds, { errorIfNegative: false });
430
+ const luxonDatetime = LuxonDateTime.fromMillis(milliseconds, options);
431
+ return new DateTime(luxonDatetime, microseconds);
424
432
  }
425
433
  /**
426
434
  * Create a DateTime from an object with date/time units.
@@ -481,6 +489,46 @@ export class DateTime {
481
489
  const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromSQL(textForLuxon, opts));
482
490
  return new DateTime(luxonDatetime, microsecond);
483
491
  }
492
+ /**
493
+ * Create a DateTime from a custom format string.
494
+ * Supports standard Luxon format tokens plus 'u' or 'SSSSSS' for microseconds (6 decimal places).
495
+ * @param text - The string to parse
496
+ * @param format - Format string using Luxon tokens (e.g., 'MM/dd/yyyy HH:mm:ss.u')
497
+ * @param opts - Optional parsing options (zone, locale, etc.)
498
+ * @returns A DateTime for the parsed instant
499
+ * @throws {InvalidDateTime} When the string doesn't match the format or is invalid
500
+ * @example
501
+ * ```ts
502
+ * DateTime.fromFormat('12/15/2017', 'MM/dd/yyyy')
503
+ * DateTime.fromFormat('12/15/2017 10:30:45', 'MM/dd/yyyy HH:mm:ss')
504
+ * DateTime.fromFormat('12/15/2017 10:30:45.123456', 'MM/dd/yyyy HH:mm:ss.u')
505
+ * DateTime.fromFormat('12/15/2017 10:30:45.123456', 'MM/dd/yyyy HH:mm:ss.SSSSSS')
506
+ * DateTime.fromFormat('mai 25, 1982', 'MMMM dd, yyyy', { locale: 'fr' })
507
+ * ```
508
+ */
509
+ static fromFormat(text, format, opts) {
510
+ // Check if format includes microsecond token ('u' or 'SSSSSS')
511
+ const hasMicrosecondToken = format.includes('.u') || format.includes('.SSSSSS');
512
+ let microsecond = 0;
513
+ if (hasMicrosecondToken) {
514
+ // Extract microseconds from the text
515
+ const { microsecond: extractedMicrosecond } = parseFractionalPart(text);
516
+ microsecond = extractedMicrosecond;
517
+ // Replace microsecond token with millisecond token for Luxon
518
+ // 'u' -> 'SSS' (Luxon only supports milliseconds)
519
+ // 'SSSSSS' -> 'SSS'
520
+ const formatForLuxon = format.replace(/\.u\b/, '.SSS').replace(/\.SSSSSS\b/, '.SSS');
521
+ // Truncate fractional part to 3 digits for Luxon
522
+ const textForLuxon = toThreeDecimalFraction(text);
523
+ const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromFormat(textForLuxon, formatForLuxon, opts));
524
+ return new DateTime(luxonDatetime, microsecond);
525
+ }
526
+ else {
527
+ // No microsecond token, use Luxon directly
528
+ const luxonDatetime = wrapLuxonError(() => LuxonDateTime.fromFormat(text, format, opts));
529
+ return new DateTime(luxonDatetime, 0);
530
+ }
531
+ }
484
532
  /**
485
533
  * Returns an ISO 8601 string with 6 fractional second digits (milliseconds + microseconds).
486
534
  * @param opts - Optional format options (includeOffset, suppressMilliseconds, etc.)
@@ -813,20 +861,38 @@ export class DateTime {
813
861
  return luxonSeconds + additionalMicroseconds;
814
862
  }
815
863
  /**
816
- * Returns the epoch time in seconds as an integer (floor of toSeconds).
817
- * Truncates any fractional seconds, returning only the whole seconds portion.
818
- * Equivalent to Math.floor(toSeconds()).
819
- * @returns Unix timestamp in seconds (integer only, no fractional part)
864
+ * Returns the epoch time in seconds as an integer, truncating any fractional part.
820
865
  * @example
821
866
  * ```ts
822
- * DateTime.fromSeconds(1707234567).toUnixInteger() // 1707234567
823
- * DateTime.fromISO('2026-02-07T09:03:44.123456Z').toUnixInteger() // 1770455024 (fractional part truncated)
824
- * DateTime.fromISO('2026-02-07T09:03:44.999999Z').toUnixInteger() // 1770455024 (not rounded up)
867
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerSeconds // 1770455024
868
+ * DateTime.fromISO('2026-02-07T09:03:44.999999Z').unixIntegerSeconds // 1770455024 (not rounded up)
825
869
  * ```
826
870
  */
827
- toUnixInteger() {
871
+ get unixIntegerSeconds() {
828
872
  return Math.floor(this.toSeconds());
829
873
  }
874
+ /**
875
+ * Returns the epoch time in milliseconds as an integer, truncating any fractional part.
876
+ * @example
877
+ * ```ts
878
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerMilliseconds // 1770455024123
879
+ * DateTime.fromISO('2026-02-07T09:03:44.123999Z').unixIntegerMilliseconds // 1770455024123 (not rounded up)
880
+ * ```
881
+ */
882
+ get unixIntegerMilliseconds() {
883
+ return Math.floor(this.toMillis());
884
+ }
885
+ /**
886
+ * Returns the epoch time in microseconds as an integer.
887
+ * Equivalent to `toMicroseconds()` since microseconds are always whole numbers.
888
+ * @example
889
+ * ```ts
890
+ * DateTime.fromISO('2026-02-07T09:03:44.123456Z').unixIntegerMicroseconds // 1770455024123456
891
+ * ```
892
+ */
893
+ get unixIntegerMicroseconds() {
894
+ return this.toMicroseconds();
895
+ }
830
896
  /**
831
897
  * Returns the earliest DateTime from the given arguments.
832
898
  * @param dateTimes - DateTimes to compare
@@ -985,58 +1051,43 @@ export class DateTime {
985
1051
  * ```
986
1052
  */
987
1053
  diff(other, unit) {
988
- // Check if we need to calculate microseconds
989
- const needsMicroseconds = unit === 'microseconds' ||
990
- (Array.isArray(unit) && unit.includes('microseconds'));
991
- // Check if we also need milliseconds
992
- const needsMilliseconds = unit === 'milliseconds' ||
993
- (Array.isArray(unit) && unit.includes('milliseconds'));
994
- // Filter out 'microseconds' from the unit array since Luxon doesn't support it
995
- let luxonUnits = unit;
996
- if (Array.isArray(unit)) {
997
- const filtered = unit.filter(u => u !== 'microseconds');
998
- luxonUnits = filtered.length > 0 ? filtered : undefined;
999
- }
1000
- else if (unit === 'microseconds') {
1001
- luxonUnits = undefined;
1002
- }
1003
- // Get Luxon's diff (which handles all units except microseconds)
1004
- const luxonDuration = this.luxonDatetime.diff(other.toLuxon(), luxonUnits);
1005
- const fullObject = luxonDuration.toObject();
1006
- // Calculate microsecond difference if needed
1007
- if (needsMicroseconds || unit === undefined) {
1008
- // When microseconds is the ONLY unit requested, return total microseconds
1054
+ const unitArray = unit === undefined ? undefined : typeof unit === 'string' ? [unit] : unit;
1055
+ const needsMicroseconds = !unitArray || unitArray.includes('microseconds');
1056
+ const needsMilliseconds = !unitArray || unitArray.includes('milliseconds');
1057
+ // Strip 'microseconds' before passing to Luxon (it doesn't support it)
1058
+ const luxonUnits = unitArray?.filter(u => u !== 'microseconds');
1059
+ const luxonDuration = this.luxonDatetime.diff(other.toLuxon(), luxonUnits && luxonUnits.length > 0 ? luxonUnits : undefined);
1060
+ const result = luxonDuration.toObject();
1061
+ if (needsMicroseconds) {
1009
1062
  if (unit === 'microseconds') {
1010
- const thisTotalMicroseconds = this.toMicroseconds();
1011
- const otherTotalMicroseconds = other.toMicroseconds();
1012
- fullObject.microseconds = thisTotalMicroseconds - otherTotalMicroseconds;
1063
+ // Only microseconds requested: return total difference
1064
+ result.microseconds = this.toMicroseconds() - other.toMicroseconds();
1013
1065
  }
1014
- else {
1015
- // When microseconds is requested with other units, return only the fractional part (0-999)
1016
- const thisMicrosecond = this.microsecond;
1017
- const otherMicrosecond = other.microsecond;
1018
- fullObject.microseconds = thisMicrosecond - otherMicrosecond;
1066
+ else if (needsMilliseconds && result.milliseconds !== undefined) {
1067
+ // Both ms + µs requested: combine Luxon's ms with µs field diff, then re-split.
1068
+ // Example: Luxon says -1ms, µs fields are 999-0=+999 → total=-1µs → 0ms, -1µs
1069
+ const totalMicroDiff = Math.round(result.milliseconds * 1000) + (this.microsecond - other.microsecond);
1070
+ result.milliseconds = Math.trunc(totalMicroDiff / 1000) || 0; // || 0 normalizes -0
1071
+ result.microseconds = totalMicroDiff - result.milliseconds * 1000;
1019
1072
  }
1020
- // If milliseconds are also requested/present, truncate them to whole numbers
1021
- // since the fractional part is now represented in microseconds
1022
- if ((needsMilliseconds || unit === undefined) && fullObject.milliseconds !== undefined) {
1023
- fullObject.milliseconds = Math.trunc(fullObject.milliseconds);
1073
+ else {
1074
+ // µs without ms: just the field difference
1075
+ result.microseconds = this.microsecond - other.microsecond;
1024
1076
  }
1025
1077
  }
1026
- // If no unit specified, return all units (including microseconds)
1027
- if (unit === undefined) {
1028
- return fullObject;
1029
- }
1030
- // If unit is a single string, return only that unit
1031
- if (typeof unit === 'string') {
1032
- return { [unit]: fullObject[unit] ?? 0 };
1033
- }
1034
- // If unit is an array, return only those units
1035
- const result = {};
1036
- for (const u of unit) {
1037
- result[u] = fullObject[u] ?? 0;
1078
+ else if (needsMilliseconds && result.milliseconds !== undefined) {
1079
+ // ms without µs: truncate fractional part
1080
+ result.milliseconds = Math.trunc(result.milliseconds);
1038
1081
  }
1039
- return result;
1082
+ // Return only the requested units
1083
+ if (unit === undefined)
1084
+ return result;
1085
+ if (typeof unit === 'string')
1086
+ return { [unit]: result[unit] ?? 0 };
1087
+ const filtered = {};
1088
+ for (const u of unit)
1089
+ filtered[u] = result[u] ?? 0;
1090
+ return filtered;
1040
1091
  }
1041
1092
  /**
1042
1093
  * Returns the difference between this DateTime and now.
@@ -1,8 +1,12 @@
1
- export default function isoTimeDecimalString(datetime, { nullIfZero }) {
1
+ export default function isoTimeDecimalString(datetime, { nullIfZero, truncateMicroseconds }) {
2
2
  const milliseconds = datetime.millisecond;
3
- const microseconds = datetime.microsecond;
4
- const totalMicroseconds = milliseconds * 1000 + microseconds;
5
- if (totalMicroseconds === 0 && nullIfZero)
3
+ if (truncateMicroseconds) {
4
+ if (nullIfZero && milliseconds === 0)
5
+ return null;
6
+ return milliseconds.toString().padStart(3, '0');
7
+ }
8
+ const totalMicroseconds = milliseconds * 1000 + datetime.microsecond;
9
+ if (nullIfZero && totalMicroseconds === 0)
6
10
  return null;
7
11
  return totalMicroseconds.toString().padStart(6, '0');
8
12
  }
@@ -1,6 +1,9 @@
1
1
  import isoTimeDecimalString from './isoTimeDecimalString.js';
2
2
  export default function replaceISOMicroseconds(timeObj, isoString, opts) {
3
- const decimalString = isoTimeDecimalString(timeObj, { nullIfZero: opts?.suppressMilliseconds });
3
+ const decimalString = isoTimeDecimalString(timeObj, {
4
+ nullIfZero: opts?.suppressMilliseconds,
5
+ truncateMicroseconds: opts?.truncateMicroseconds,
6
+ });
4
7
  if (decimalString === null)
5
8
  return isoString;
6
9
  // Match time in both ISO format (with T) and SQL format (with space)
@@ -34,11 +34,13 @@ export interface ToISOTimeOptions {
34
34
  includeOffset?: boolean;
35
35
  includePrefix?: boolean;
36
36
  format?: 'basic' | 'extended';
37
+ truncateMicroseconds?: boolean;
37
38
  }
38
39
  export interface ToSQLOptions {
39
40
  includeZone?: boolean;
40
41
  includeOffset?: boolean;
41
42
  includeOffsetSpace?: boolean;
43
+ truncateMicroseconds?: boolean;
42
44
  }
43
45
  export type DateTimeUnit = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond';
44
46
  /**
@@ -40,12 +40,14 @@ export interface ToISOTimeOptions {
40
40
  includeOffset?: boolean
41
41
  includePrefix?: boolean
42
42
  format?: 'basic' | 'extended'
43
+ truncateMicroseconds?: boolean
43
44
  }
44
45
 
45
46
  export interface ToSQLOptions {
46
47
  includeZone?: boolean
47
48
  includeOffset?: boolean
48
49
  includeOffsetSpace?: boolean
50
+ truncateMicroseconds?: boolean
49
51
  }
50
52
 
51
53
  export type DateTimeUnit =