Skip to content

API Reference

/

Values

Dates, times and intervals

SurrealDB 4.0 adds four types for calendar and clock values that are not an instant in UTC: date for a calendar day, time for a time of day, timestamp for a date and a time of day with no time zone, and interval for a calendar period such as one month and two days. The Python SDK has a class for each: PlainDate, PlainTime, Timestamp and Interval. This page documents the four classes and how they convert. For an instant in time, use a datetime, and for a fixed length of time, a Duration.

Import
from surrealdb import Interval, PlainDate, PlainTime, Timestamp

Source: temporal.py

Over cbor.2, which SurrealDB 4.0 servers speak, each class is sent as its own type and reads back as the same class. A datetime.date and a naive datetime.time are sent as a date and a time too, and read back as a PlainDate and a PlainTime. Servers before 4.0 speak plain cbor and have none of these types, so the SDK sends the nearest type that the server has.

PythonOver cbor.2 (SurrealDB 4.0)Over cbor (earlier servers)
PlainDate, datetime.datedate, read back as PlainDateA datetime at midnight UTC, read back as PreciseDatetime
PlainTime, naive datetime.timetime, read back as PlainTimeA duration since midnight, read back as Duration
Timestamptimestamp, read back as TimestampA datetime, with the wall-clock time read as UTC, read back as PreciseDatetime
Intervalinterval, read back as IntervalIts ISO 8601 text as a string, read back as str

A datetime.datetime is a subclass of datetime.date, but the SDK still sends it as a datetime. A datetime.time with a time zone raises ValueError, because a SurrealDB time has no zone. db.wire_format tells you which format a connection uses.

import datetime as dt
from surrealdb import PlainDate, RecordID, Surreal

with Surreal("ws://localhost:8000") as db:
    db.signin({"username": "root", "password": "secret"})
    db.use("surrealdb", "docs")

    db.create(RecordID("person", "tobie"), {
        "birthday": PlainDate(1990, 5, 17),
        "alarm": dt.time(7, 30),
    })

    person = db.select(RecordID("person", "tobie")).execute()
    print(repr(person["birthday"]), repr(person["alarm"]))
Output with SurrealDB 4.0
PlainDate(1990, 5, 17) PlainTime(7, 30)

Against an earlier server, the same code stores a datetime and a duration, which read back as PreciseDatetime(1990, 5, 17, 0, 0, tzinfo=datetime.timezone.utc) and Duration(elapsed=27000000000000). Over plain cbor, a date outside the years 1 to 9999, such as PlainDate.INFINITY, becomes a datetime that Python cannot read back, and reading it raises UnexpectedResponseError.

A PlainDate is a calendar date with no time of day and no time zone, for the SurrealDB date type.

Syntax
PlainDate(year, month, day)

Parameter

Type

Description

yearint

The year, from

-262143

to

262142

.

monthint

The month, from

1

to

12

.

dayint

The day of the month.

A date that does not exist, such as 2023-02-29, raises ValueError, and an argument that is not an int raises TypeError. PlainDate.INFINITY and PlainDate.NEG_INFINITY are the last and first dates that SurrealDB holds. They print as infinity and -infinity, and they sort after and before every other date.

Property or methodReturnsDescription
year, month, dayintThe parts of the date.
daysintThe number of days since 1970-01-01, negative before it.
is_infiniteboolWhether the date is INFINITY or NEG_INFINITY.
to_date()datetime.dateThe same date. Raises ValueError outside the years 1 to 9999, which includes the infinities.
PlainDate.from_date(value)PlainDateThe calendar date of a datetime.date or datetime.datetime.
PlainDate.parse(text)PlainDateA date as SurrealDB prints one, such as 2024-02-29, +10000-01-01 or infinity.
PlainDate.from_days(days)PlainDateThe date days after 1970-01-01.

str() gives the date as SurrealDB prints it, with a sign before a year outside 0 to 9999. A PlainDate compares and sorts with other PlainDate values, and is never equal to a datetime.date, so convert one of the two before you compare them.

import datetime as dt
from surrealdb import PlainDate

day = PlainDate(2024, 2, 29)
print(day, day.days, day.to_date())
print(PlainDate.parse("+10000-01-01").year)
print(PlainDate.INFINITY, day < PlainDate.INFINITY)
print(day == dt.date(2024, 2, 29), day.to_date() == dt.date(2024, 2, 29))
Output
2024-02-29 19782 2024-02-29
10000
infinity True
False True

A PlainTime is a time of day with no date and no time zone, to the nanosecond, for the SurrealDB time type. It runs from 0000 up to and including 2400.

Syntax
PlainTime(hour=0, minute=0, second=0, microsecond=0, nanosecond=0)

Parameter

Type

Description

hourint

From

0

to

24

. Hour

24

is allowed only for 2400.

minute

,

second
int

From

0

to

59

.

microsecondint

From

0

to

999999

.

nanosecondint

The nanoseconds after

microsecond

, from

0

to

999

.

Property or methodReturnsDescription
hour, minute, second, microsecond, nanosecondintThe parts of the time.
nanosecondsintThe number of nanoseconds since midnight.
to_time()datetime.timeThe same time without its nanosecond. Raises ValueError for 2400, which datetime.time does not hold.
PlainTime.from_time(value)PlainTimeThe time of a naive datetime.time. Raises ValueError for a time with a time zone.
PlainTime.parse(text)PlainTimeA time as SurrealDB prints one, such as 12:34:56.789 or 24:00:00.
PlainTime.from_nanoseconds(nanoseconds)PlainTimeThe time nanoseconds after midnight.

A PlainTime compares and sorts with other PlainTime values, and is never equal to a datetime.time.

import datetime as dt
from surrealdb import PlainTime

alarm = PlainTime(7, 30)
print(alarm, alarm.nanoseconds)
print(PlainTime.parse("12:34:56.789000005").nanosecond)
print(PlainTime(24), PlainTime.from_time(dt.time(9, 15)).to_time())
Output
07:30:00 27000000000000
5
24:00:00 09:15:00

A Timestamp is a date and a time of day with no time zone, to the nanosecond, for the SurrealDB timestamp type. A timestamp is a wall-clock reading, where a datetime is an instant in UTC. The SDK still sends a datetime.datetime as a datetime, so convert a naive one with Timestamp.from_datetime() to send it as a timestamp.

Syntax
Timestamp(
    year, month, day,
    hour=0, minute=0, second=0, microsecond=0, nanosecond=0,
)

Parameter

Type

Description

year

,

month

,

day
int

The date, as for

PlainDate

.

hourint

From

0

to

23

.

minute

,

second

,

microsecond

,

nanosecond
int

As for

PlainTime

.

Timestamp.INFINITY and Timestamp.NEG_INFINITY are the last and first instants that SurrealDB holds, and they print as infinity and -infinity.

Property or methodReturnsDescription
year, month, day, hour, minute, second, microsecond, nanosecondintThe parts of the timestamp.
seconds_and_nanostuple[int, int]The seconds since 1970-01-01T0000, and the nanoseconds within the second.
is_infiniteboolWhether the timestamp is INFINITY or NEG_INFINITY.
to_datetime()PreciseDatetimeA naive datetime with the same wall-clock time, nanoseconds included. Raises ValueError outside the years 1 to 9999.
Timestamp.from_datetime(value)TimestampThe wall-clock time of a naive datetime, with the nanoseconds of a PreciseDatetime. Raises ValueError for a datetime with a time zone.
Timestamp.parse(text)TimestampA timestamp as SurrealDB prints one, such as 2024-02-29T12:34:56.789, or infinity. Text with a time zone raises ValueError.

A Timestamp compares and sorts with other Timestamp values, and is never equal to a datetime.

import datetime as dt
from surrealdb import Timestamp

meeting = Timestamp(2024, 2, 29, 9, 30)
print(meeting, repr(meeting.to_datetime()))

wall_clock = dt.datetime(2024, 2, 29, 9, 30)
print(Timestamp.from_datetime(wall_clock) == meeting)
print(Timestamp.parse("2024-02-29 09:30:00.000000001").nanosecond)
Output
2024-02-29T09:30:00 PreciseDatetime(2024, 2, 29, 9, 30)
True
1

An Interval is a calendar period, for the SurrealDB interval type. An interval keeps its months, days and microseconds apart, each with its own sign, because neither a month nor a day has a fixed length on a calendar. Years go into the months, weeks into the days, and hours, minutes and seconds into the microseconds.

Syntax
Interval(months=0, days=0, microseconds=0)

Parameter

Type

Description

monthsint

The months, including twelve for each year. A signed 32-bit integer.

daysint

The days, including seven for each week. A signed 32-bit integer.

microsecondsint

The hours, minutes and seconds, in microseconds. A signed 64-bit integer.

The three parts are the months, days and microseconds properties, and str() gives the interval as ISO 8601 text, as SurrealDB prints it. An empty interval prints as PT0S.

Two intervals are equal when they are equal as the server compares them: with a month counted as 30 days and a day as 24 hours, so Interval(months=1) == Interval(days=30) is true, although each keeps its parts as written. Compare the (months, days, microseconds) parts to tell such intervals apart. An Interval is hashable, but intervals have no order, so < raises TypeError.

from surrealdb import Interval

renewal = Interval(months=14, days=3, microseconds=14_706_500_000)
print(renewal)
print(Interval(months=1) == Interval(days=30))
print(Interval(months=1).days, Interval(days=30).days)
Output
P1Y2M3DT4H5M6.5S
True
0 30

Was this page helpful?