DateTime
Working with dates and times in JavaScript can be challenging. The built-in Date object mutates its internal state, and time zone handling can be confusing. These design choices can lead to errors when working on applications that rely on date-time accuracy, such as scheduling systems, timestamping services, or logging utilities.
The DateTime module aims to address these limitations by offering:
- Immutable Data: Each
DateTimeis an immutable structure, reducing mistakes related to in-place mutations. - Time Zone Support:
DateTimeprovides robust support for time zones, including automatic daylight saving time adjustments. - Arithmetic Operations: You can perform arithmetic operations on
DateTimeinstances, such as adding or subtracting durations.
The DateTime Type
A DateTime represents a moment in time. It can be stored as either a simple UTC value or as a value with an associated time zone. Storing time this way helps you manage both precise timestamps and the context for how that time should be displayed or interpreted.
There are two main variants of DateTime:
-
Utc: An immutable structure that uses
epochMillis(milliseconds since the Unix epoch) to represent a point in time in Coordinated Universal Time (UTC). -
Zoned: Includes
epochMillisalong with aTimeZone, allowing you to attach an offset or a named region (like “America/New_York”) to the timestamp.
Why Have Two Variants?
- Utc is straightforward if you only need a universal reference without relying on local time zones.
- Zoned is helpful when you need to keep track of time zone information for tasks such as converting to local times or adjusting for daylight saving time.
TimeZone Variants
A TimeZone can be either:
- Offset: Represents a fixed offset from UTC (for example, UTC+2 or UTC-5).
- Named: Uses a named region (e.g., “Europe/London” or “America/New_York”) that automatically accounts for region-specific rules like daylight saving time changes.
TypeScript Definition
Below is the TypeScript definition for the DateTime type:
type DateTime = Utc | Zoned
interface Utc { readonly _tag: "Utc" readonly epochMillis: number}
interface Zoned { readonly _tag: "Zoned" readonly epochMillis: number readonly zone: TimeZone}
type TimeZone = TimeZone.Offset | TimeZone.Named
declare namespace TimeZone { interface Offset { readonly _tag: "Offset" readonly offset: number }
interface Named { readonly _tag: "Named" readonly id: string }}The DateTime.Parts Type
The DateTime.Parts type defines the main components of a date, such as the year, month, day, hours, minutes, and seconds.
namespace DateTime { interface Parts { readonly millis: number readonly seconds: number readonly minutes: number readonly hours: number readonly day: number readonly month: number readonly year: number }
interface PartsWithWeekday extends Parts { readonly weekDay: number }}The DateTime.Input Type
The DateTime.Input type is a flexible input type that can be used to create a DateTime instance. It can be one of the following:
- A
DateTimeinstance - A JavaScript
Dateobject - A numeric value representing milliseconds since the Unix epoch
- An object with partial date parts (e.g.,
{ year: 2024, month: 1, day: 1 }) - A string that can be parsed by JavaScript’s Date.parse
namespace DateTime { type Input = DateTime | Partial<Parts> | Date | number | string}Utc Constructors
Utc is an immutable structure that uses epochMillis (milliseconds since the Unix epoch) to represent a point in time in Coordinated Universal Time (UTC).
unsafeFromDate
Creates a Utc from a JavaScript Date.
Throws an IllegalArgumentException if the provided Date is invalid.
When a Date object is passed, it is converted to a Utc instance. The time is interpreted as the local time of the system executing the code and then adjusted to UTC. This ensures a consistent, timezone-independent representation of the date and time.
Example (Converting Local Time to UTC in Italy)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// Create a Utc instance from a local JavaScript Date//// ┌─── Utc// ▼const utc = DateTime.unsafeFromDate(new Date("2025-01-01 04:00:00"))
console.log(utc)// Output: DateTime.Utc(2025-01-01T03:00:00.000Z)
console.log(utc.epochMillis)// Output: 1735700400000Explanation:
- The local time 2025-01-01 04:00:00 (in Italy, CET) is converted to UTC by subtracting the timezone offset (UTC+1 in January).
- As a result, the UTC time becomes 2025-01-01 03:00:00.000Z.
epochMillisprovides the same time as milliseconds since the Unix Epoch, ensuring a precise numeric representation of the UTC timestamp.
unsafeMake
Creates a Utc from a DateTime.Input.
Example (Creating a DateTime with unsafeMake)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// From a JavaScript Dateconst utc1 = DateTime.unsafeMake(new Date("2025-01-01 04:00:00"))console.log(utc1)// Output: DateTime.Utc(2025-01-01T03:00:00.000Z)
// From partial date partsconst utc2 = DateTime.unsafeMake({ year: 2025 })console.log(utc2)// Output: DateTime.Utc(2025-01-01T00:00:00.000Z)
// From a stringconst utc3 = DateTime.unsafeMake("2025-01-01")console.log(utc3)// Output: DateTime.Utc(2025-01-01T00:00:00.000Z)Explanation:
- The local time 2025-01-01 04:00:00 (in Italy, CET) is converted to UTC by subtracting the timezone offset (UTC+1 in January).
- As a result, the UTC time becomes 2025-01-01 03:00:00.000Z.
make
Similar to unsafeMake, but returns an Option instead of throwing an error if the input is invalid.
If the input is invalid, it returns None. If valid, it returns Some containing the Utc.
Example (Creating a DateTime Safely)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// From a JavaScript Dateconst maybeUtc1 = DateTime.make(new Date("2025-01-01 04:00:00"))console.log(maybeUtc1)/*Output:{ _id: 'Option', _tag: 'Some', value: '2025-01-01T03:00:00.000Z' }*/
// From partial date partsconst maybeUtc2 = DateTime.make({ year: 2025 })console.log(maybeUtc2)/*Output:{ _id: 'Option', _tag: 'Some', value: '2025-01-01T00:00:00.000Z' }*/
// From a stringconst maybeUtc3 = DateTime.make("2025-01-01")console.log(maybeUtc3)/*Output:{ _id: 'Option', _tag: 'Some', value: '2025-01-01T00:00:00.000Z' }*/Explanation:
- The local time 2025-01-01 04:00:00 (in Italy, CET) is converted to UTC by subtracting the timezone offset (UTC+1 in January).
- As a result, the UTC time becomes 2025-01-01 03:00:00.000Z.
Zoned Constructors
A Zoned includes epochMillis along with a TimeZone, allowing you to attach an offset or a named region (like “America/New_York”) to the timestamp.
unsafeMakeZoned
Creates a Zoned by combining a DateTime.Input with an optional TimeZone.
This allows you to represent a specific point in time with an associated time zone.
The time zone can be provided in several ways:
- As a
TimeZoneobject - A string identifier (e.g.,
"Europe/London") - A numeric offset in milliseconds
If the input or time zone is invalid, an IllegalArgumentException is thrown.
Example (Creating a Zoned DateTime Without Specifying a Time Zone)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// Create a Zoned DateTime based on the system's local time zoneconst zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00"))
console.log(zoned)// Output: DateTime.Zoned(2025-01-01T04:00:00.000+01:00)
console.log(zoned.zone)// Output: TimeZone.Offset(+01:00)Here, the system’s time zone (CET, which is UTC+1 in January) is used to create the Zoned instance.
Example (Specifying a Named Time Zone)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// Create a Zoned DateTime with a specified named time zoneconst zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome",})
console.log(zoned)// Output: DateTime.Zoned(2025-01-01T04:00:00.000+01:00[Europe/Rome])
console.log(zoned.zone)// Output: TimeZone.Named(Europe/Rome)In this case, the "Europe/Rome" time zone is explicitly provided, resulting in the Zoned instance being tied to this named time zone.
By default, the input date is treated as a UTC value and then adjusted for the specified time zone. To interpret the input date as being in the specified time zone, you can use the adjustForTimeZone option.
Example (Adjusting for Time Zone Interpretation)
The following example assumes the code is executed on a system in Italy (CET timezone):
import { DateTime } from "effect"
// Interpret the input date as being in the specified time zoneconst zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome", adjustForTimeZone: true,})
console.log(zoned)// Output: DateTime.Zoned(2025-01-01T03:00:00.000+01:00[Europe/Rome])
console.log(zoned.zone)// Output: TimeZone.Named(Europe/Rome)Explanation
- Without
adjustForTimeZone: The input date is interpreted as UTC and then adjusted to the specified time zone. For instance,2025-01-01 04:00:00in UTC becomes2025-01-01T04:00:00.000+01:00in CET (UTC+1). - With
adjustForTimeZone: true: The input date is interpreted as being in the specified time zone. For example,2025-01-01 04:00:00in “Europe/Rome” (CET) is adjusted to its corresponding UTC time, resulting in2025-01-01T03:00:00.000+01:00.
makeZoned
The makeZoned function works similarly to unsafeMakeZoned but provides a safer approach. Instead of throwing an error when the input is invalid, it returns an Option<Zoned>.
If the input is invalid, it returns None. If valid, it returns Some containing the Zoned.
Example (Safely Creating a Zoned DateTime)
import { DateTime, Option } from "effect"
// ┌─── Option<Zoned>// ▼const zoned = DateTime.makeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome",})
if (Option.isSome(zoned)) { console.log("The DateTime is valid")}makeZonedFromString
Creates a Zoned by parsing a string in the format YYYY-MM-DDTHH:mm:ss.sss+HH:MM[IANA timezone identifier].
If the input string is valid, the function returns a Some containing the Zoned. If the input is invalid, it returns None.
Example (Parsing a Zoned DateTime from a String)
import { DateTime, Option } from "effect"
// ┌─── Option<Zoned>// ▼const zoned = DateTime.makeZonedFromString("2025-01-01T03:00:00.000+01:00[Europe/Rome]")
if (Option.isSome(zoned)) { console.log("The DateTime is valid")}Current Time
now
Provides the current UTC time as a Effect<Utc>, using the Clock service.
Example (Retrieving the Current UTC Time)
import { DateTime, Effect } from "effect"
const program = Effect.gen(function* () { // ┌─── Utc // ▼ const currentTime = yield* DateTime.now})unsafeNow
Retrieves the current UTC time immediately using Date.now(), without the Clock service.
Example (Getting the Current UTC Time Immediately)
import { DateTime } from "effect"
// ┌─── Utc// ▼const currentTime = DateTime.unsafeNow()Guards
| Function | Description |
|---|---|
isDateTime |
Checks if a value is a DateTime. |
isTimeZone |
Checks if a value is a TimeZone. |
isTimeZoneOffset |
Checks if a value is a TimeZone.Offset. |
isTimeZoneNamed |
Checks if a value is a TimeZone.Named. |
isUtc |
Checks if a DateTime is the Utc variant. |
isZoned |
Checks if a DateTime is the Zoned variant. |
Example (Validating a DateTime)
import { DateTime } from "effect"
function printDateTimeInfo(x: unknown) { if (DateTime.isDateTime(x)) { console.log("This is a valid DateTime") } else { console.log("Not a DateTime") }}Time Zone Management
| Function | Description |
|---|---|
setZone |
Creates a Zoned from DateTime by applying the given TimeZone. |
setZoneOffset |
Creates a Zoned from DateTime using a fixed offset (in ms). |
setZoneNamed |
Creates a Zoned from DateTime from an IANA time zone identifier or returns None if invalid. |
unsafeSetZoneNamed |
Creates a Zoned from DateTime from an IANA time zone identifier or throws if invalid. |
zoneUnsafeMakeNamed |
Creates a TimeZone.Named from a IANA time zone identifier or throws if the identifier is invalid. |
zoneMakeNamed |
Creates a TimeZone.Named from a IANA time zone identifier or returns None if invalid. |
zoneMakeNamedEffect |
Creates a Effect<TimeZone.Named, IllegalArgumentException> from a IANA time zone identifier failing with IllegalArgumentException if invalid |
zoneMakeOffset |
Creates a TimeZone.Offset from a numeric offset in milliseconds. |
zoneMakeLocal |
Creates a TimeZone.Named from the system’s local time zone. |
zoneFromString |
Attempts to parse a time zone from a string, returning None if invalid. |
zoneToString |
Returns a string representation of a TimeZone. |
Example (Applying a Time Zone to a DateTime)
import { DateTime } from "effect"
// Create a UTC DateTime//// ┌─── Utc// ▼const utc = DateTime.unsafeMake("2024-01-01")
// Create a named time zone for New York//// ┌─── TimeZone.Named// ▼const zoneNY = DateTime.zoneUnsafeMakeNamed("America/New_York")
// Apply it to the DateTime//// ┌─── Zoned// ▼const zoned = DateTime.setZone(utc, zoneNY)
console.log(zoned)// Output: DateTime.Zoned(2023-12-31T19:00:00.000-05:00[America/New_York])zoneFromString
Parses a string to create a DateTime.TimeZone.
This function attempts to interpret the input string as either:
- A numeric time zone offset (e.g., “GMT”, “+01:00”)
- An IANA time zone identifier (e.g., “Europe/London”)
If the string matches an offset format, it is converted into a TimeZone.Offset.
Otherwise, it attempts to create a TimeZone.Named using the input.
If the input string is invalid, Option.none() is returned.
Example (Parsing a Time Zone from a String)
import { DateTime, Option } from "effect"
// Attempt to parse a numeric offsetconst offsetZone = DateTime.zoneFromString("+01:00")console.log(Option.isSome(offsetZone))// Output: true
// Attempt to parse an IANA time zoneconst namedZone = DateTime.zoneFromString("Europe/London")console.log(Option.isSome(namedZone))// Output: true
// Invalid inputconst invalidZone = DateTime.zoneFromString("Invalid/Zone")console.log(Option.isSome(invalidZone))// Output: falseComparisons
| Function | Description |
|---|---|
distance |
Returns the difference (in ms) between two DateTimes. |
distanceDurationEither |
Returns a Left or Right Duration depending on order. |
distanceDuration |
Returns a Duration indicating how far apart two times are. |
min |
Returns the earlier of two DateTime values. |
max |
Returns the later of two DateTime values. |
greaterThan, greaterThanOrEqualTo, etc. |
Checks ordering between two DateTime values. |
between |
Checks if a DateTime lies within the given bounds. |
isFuture, isPast, unsafeIsFuture, etc. |
Checks if a DateTime is in the future or past. |
Example (Finding the Distance Between Two DateTimes)
import { DateTime } from "effect"
const utc1 = DateTime.unsafeMake("2025-01-01T00:00:00Z")const utc2 = DateTime.add(utc1, { days: 1 })
console.log(DateTime.distance(utc1, utc2))// Output: 86400000 (one day)
console.log(DateTime.distanceDurationEither(utc1, utc2))/*Output:{ _id: 'Either', _tag: 'Right', right: { _id: 'Duration', _tag: 'Millis', millis: 86400000 }}*/
console.log(DateTime.distanceDuration(utc1, utc2))// Output: { _id: 'Duration', _tag: 'Millis', millis: 86400000 }Conversions
| Function | Description |
|---|---|
toDateUtc |
Returns a JavaScript Date in UTC. |
toDate |
Applies the time zone (if present) and converts to a JavaScript Date. |
zonedOffset |
For a Zoned DateTime, returns the time zone offset in ms. |
zonedOffsetIso |
For a Zoned DateTime, returns an ISO offset string like “+01:00”. |
toEpochMillis |
Returns the Unix epoch time in milliseconds. |
removeTime |
Returns a Utc with the time cleared (only date remains). |
Parts
| Function | Description |
|---|---|
toParts |
Returns time zone adjusted date parts (including weekday). |
toPartsUtc |
Returns UTC date parts (including weekday). |
getPart / getPartUtc |
Retrieves a specific part (e.g., "year" or "month") from the date. |
setParts / setPartsUtc |
Updates certain parts of a date, preserving or ignoring the time zone. |
Example (Extracting Parts from a DateTime)
import { DateTime } from "effect"
const zoned = DateTime.setZone( DateTime.unsafeMake("2024-01-01"), DateTime.zoneUnsafeMakeNamed("Europe/Rome"),)
console.log(DateTime.getPart(zoned, "month"))// Output: 1Math
| Function | Description |
|---|---|
addDuration |
Adds the given Duration to a DateTime. |
subtractDuration |
Subtracts the given Duration from a DateTime. |
add |
Adds numeric parts (e.g., { hours: 2 }) to a DateTime. |
subtract |
Subtracts numeric parts. |
startOf |
Moves a DateTime to the start of the given unit (e.g., the beginning of a day or month). |
endOf |
Moves a DateTime to the end of the given unit. |
nearest |
Rounds a DateTime to the nearest specified unit. |
Formatting
| Function | Description |
|---|---|
format |
Formats a DateTime as a string using the DateTimeFormat API. |
formatLocal |
Uses the system’s local time zone and locale for formatting. |
formatUtc |
Forces UTC formatting. |
formatIntl |
Uses a provided Intl.DateTimeFormat. |
formatIso |
Returns an ISO 8601 string in UTC. |
formatIsoDate |
Returns an ISO date string, adjusted for the time zone. |
formatIsoDateUtc |
Returns an ISO date string in UTC. |
formatIsoOffset |
Formats a Zoned as a string with an offset like “+01:00”. |
formatIsoZoned |
Formats a Zoned in the form YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Zone]. |
Layers for Current Time Zone
| Function | Description |
|---|---|
CurrentTimeZone |
A service tag for the current time zone. |
setZoneCurrent |
Sets a DateTime to use the current time zone. |
withCurrentZone |
Provides an effect with a specified time zone. |
withCurrentZoneLocal |
Uses the system’s local time zone for the effect. |
withCurrentZoneOffset |
Uses a fixed offset (in ms) for the effect. |
withCurrentZoneNamed |
Uses a named time zone identifier (e.g., “Europe/London”). |
nowInCurrentZone |
Retrieves the current time as a Zoned in the configured time zone. |
layerCurrentZone |
Creates a Layer providing the CurrentTimeZone service. |
layerCurrentZoneOffset |
Creates a Layer from a fixed offset. |
layerCurrentZoneNamed |
Creates a Layer from a named time zone, failing if invalid. |
layerCurrentZoneLocal |
Creates a Layer from the system’s local time zone. |
Example (Using the Current Time Zone in an Effect)
import { DateTime, Effect } from "effect"
// Retrieve the current time in the "Europe/London" time zoneconst program = Effect.gen(function* () { const zonedNow = yield* DateTime.nowInCurrentZone console.log(zonedNow)}).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
Effect.runFork(program)/*Example Output:DateTime.Zoned(2025-01-06T18:36:38.573+00:00[Europe/London])*/