diff --git a/.changeset/bright-clocks-count.md b/.changeset/bright-clocks-count.md
new file mode 100644
index 00000000000..8f3df8ee01a
--- /dev/null
+++ b/.changeset/bright-clocks-count.md
@@ -0,0 +1,5 @@
+---
+"effect": patch
+---
+
+Add `DateTime.toEpochSeconds` and `DateTime.fromEpochSeconds` for converting date-time values to and from Unix epoch seconds.
diff --git a/packages/effect/src/DateTime.ts b/packages/effect/src/DateTime.ts
index afa1ab1f9c6..c121861a27a 100644
--- a/packages/effect/src/DateTime.ts
+++ b/packages/effect/src/DateTime.ts
@@ -652,6 +652,22 @@ export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe
*/
export const makeUnsafe: (input: A) => DateTime.PreserveZone = Internal.makeUnsafe
+/**
+ * Creates a `DateTime.Utc` from the number of seconds since the Unix epoch.
+ *
+ * **Example** (Creating from epoch seconds)
+ *
+ * ```ts import.meta.vitest
+ * import { DateTime } from "effect"
+ *
+ * DateTime.fromEpochSeconds(1704067200).toJSON() // => "2024-01-01T00:00:00.000Z"
+ * ```
+ *
+ * @category constructors
+ * @since 4.0.0
+ */
+export const fromEpochSeconds: (seconds: number) => Utc = Internal.fromEpochSeconds
+
/**
* Create a `DateTime.Zoned` using `DateTime.makeUnsafe` and a time zone.
*
@@ -1600,6 +1616,28 @@ export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso
*/
export const toEpochMillis: (self: DateTime) => number = Internal.toEpochMillis
+/**
+ * Converts a `DateTime` to the number of seconds since the Unix epoch.
+ *
+ * **Details**
+ *
+ * This returns the UTC timestamp regardless of any time zone information.
+ * The result is floored to the nearest second.
+ *
+ * **Example** (Reading epoch seconds)
+ *
+ * ```ts import.meta.vitest
+ * import { DateTime } from "effect"
+ *
+ * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z")
+ * DateTime.toEpochSeconds(dt) // => 1704067200
+ * ```
+ *
+ * @category converting
+ * @since 4.0.0
+ */
+export const toEpochSeconds: (self: DateTime) => number = Internal.toEpochSeconds
+
/**
* Removes the time aspect of a `DateTime`, first adjusting for the time
* zone. It will return a `DateTime.Utc` only containing the date.
diff --git a/packages/effect/src/internal/dateTime.ts b/packages/effect/src/internal/dateTime.ts
index 63a9c2cd8b9..287ae8ef94e 100644
--- a/packages/effect/src/internal/dateTime.ts
+++ b/packages/effect/src/internal/dateTime.ts
@@ -600,6 +600,12 @@ export const zonedOffsetIso = (self: DateTime.Zoned): string => offsetToString(z
/** @internal */
export const toEpochMillis = (self: DateTime.DateTime): number => self.epochMilliseconds
+/** @internal */
+export const toEpochSeconds = (self: DateTime.DateTime): number => Math.floor(self.epochMilliseconds / 1000)
+
+/** @internal */
+export const fromEpochSeconds = (seconds: number): DateTime.Utc => makeUtc(seconds * 1000)
+
/** @internal */
export const removeTime = (self: DateTime.DateTime): DateTime.Utc =>
withDate(self, (date) => {
diff --git a/packages/effect/test/DateTime.test.ts b/packages/effect/test/DateTime.test.ts
index 36555333037..88d68b0db48 100644
--- a/packages/effect/test/DateTime.test.ts
+++ b/packages/effect/test/DateTime.test.ts
@@ -381,6 +381,32 @@ describe("DateTime", () => {
})
})
+ describe("toEpochSeconds", () => {
+ it("returns epoch seconds", () => {
+ const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z")
+ strictEqual(DateTime.toEpochSeconds(dt), 1704067200)
+ })
+
+ it("floors to nearest second", () => {
+ const dt = DateTime.makeUnsafe("2024-01-01T00:00:00.999Z")
+ strictEqual(DateTime.toEpochSeconds(dt), 1704067200)
+ })
+ })
+
+ describe("fromEpochSeconds", () => {
+ it("creates DateTime from epoch seconds", () => {
+ const dt = DateTime.fromEpochSeconds(1704067200)
+ strictEqual(dt.toJSON(), "2024-01-01T00:00:00.000Z")
+ })
+
+ it("roundtrips with toEpochSeconds", () => {
+ const original = DateTime.makeUnsafe("2024-06-15T12:30:00Z")
+ const seconds = DateTime.toEpochSeconds(original)
+ const restored = DateTime.fromEpochSeconds(seconds)
+ strictEqual(DateTime.toEpochSeconds(restored), seconds)
+ })
+ })
+
describe("makeZonedFromString", () => {
it.effect("parses an instant with an offset and IANA zone", () =>
Effect.gen(function*() {