Skip to content

Date & Time

All timestamps in Sema are UTC Unix timestamps — the number of seconds since January 1, 1970 00:00:00 UTC. Timestamps are floating-point numbers with millisecond fractional precision.

TIP

All time/ functions operate in UTC. There is no timezone conversion support — if you need local time handling, compute the offset manually with time/add.

Current Time

time/now

Return the current time as a UTC Unix timestamp in seconds, with fractional milliseconds.

sema
(time/now)   ; => 1707955200.123

The integer part is seconds since the Unix epoch; the fractional part provides millisecond precision.

sema
(define now (time/now))
(println "Current timestamp: " now)

;; Extract just the seconds (truncate fractional part)
(define whole-seconds (floor now))

time-ms

Return the current time as Unix milliseconds (integer). Defined in the system module but useful alongside datetime operations.

sema
(time-ms)   ; => 1707955200123

Formatting

time/format

Format a UTC Unix timestamp using a strftime-style format string.

sema
(time/format timestamp format-string) ; => string
sema
(define ts 1736943000.0)  ; 2025-01-15 12:10:00 UTC

(time/format ts "%Y-%m-%d")            ; => "2025-01-15"
(time/format ts "%H:%M:%S")            ; => "12:10:00"
(time/format ts "%Y-%m-%d %H:%M:%S")   ; => "2025-01-15 12:10:00"
(time/format ts "%A, %B %d, %Y")       ; => "Wednesday, January 15, 2025"
(time/format ts "%F")                  ; => "2025-01-15"  (shorthand for %Y-%m-%d)
(time/format ts "%T")                  ; => "12:10:00"    (shorthand for %H:%M:%S)

Parsing

time/parse

Parse a date string into a UTC Unix timestamp using a strftime-style format string. The input is treated as a UTC naive datetime — no timezone information is expected or applied.

sema
(time/parse date-string format-string) ; => float (UTC timestamp)
sema
(time/parse "2025-01-15 12:10:00" "%Y-%m-%d %H:%M:%S")    ; => 1736943000.0
(time/parse "2025-01-15 00:00:00" "%Y-%m-%d %H:%M:%S")    ; => 1736899200.0
(time/parse "15/01/2025 14:30:00" "%d/%m/%Y %H:%M:%S")    ; => 1736951400.0

INFO

The format string must provide enough directives to fully specify a date and time. Parsing a date-only string like "%Y-%m-%d" without time components will fail — always include time directives (e.g., %H:%M:%S).

Roundtrip — formatting a timestamp and parsing it back yields the original value:

sema
(define ts 1700000000.0)
(define formatted (time/format ts "%Y-%m-%d %H:%M:%S"))
(define parsed (time/parse formatted "%Y-%m-%d %H:%M:%S"))
(= parsed ts)  ; => #t

WARNING

time/parse returns whole seconds — sub-second precision from the original timestamp is lost when roundtripping through format/parse.

Date Decomposition

time/date-parts

Decompose a UTC Unix timestamp into a map of date/time components.

sema
(time/date-parts timestamp) ; => map
sema
(define ts 1736943000.0)  ; 2025-01-15 12:10:00 UTC
(define parts (time/date-parts ts))

(get parts :year)     ; => 2025
(get parts :month)    ; => 1
(get parts :day)      ; => 15
(get parts :hour)     ; => 12
(get parts :minute)   ; => 10
(get parts :second)   ; => 0
(get parts :weekday)  ; => "Wednesday"

The returned map contains these keys:

KeyTypeDescriptionExample
:yearintegerFour-digit year2025
:monthintegerMonth (1–12)1
:dayintegerDay of month (1–31)15
:hourintegerHour (0–23)12
:minuteintegerMinute (0–59)10
:secondintegerSecond (0–59)0
:weekdaystringFull weekday name"Wednesday"

The :weekday value is the full English weekday name: "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday".

Arithmetic

time/add

Add seconds to a timestamp. Returns a new timestamp. Use negative values to subtract.

sema
(time/add timestamp seconds) ; => float (timestamp)
sema
(define ts 1736943000.0)  ; 2025-01-15 12:10:00 UTC

(time/add ts 3600)       ; one hour later    => 1736946600.0
(time/add ts 86400)      ; one day later     => 1737029400.0
(time/add ts -3600)      ; one hour earlier  => 1736939400.0
(time/add ts (* 7 86400)) ; one week later

Common durations in seconds:

DurationSeconds
1 minute60
1 hour3600
1 day86400
1 week604800
30 days2592000

time/diff

Compute the difference between two timestamps in seconds. Returns t1 - t2 (the first argument minus the second). The result can be negative.

sema
(time/diff t1 t2) ; => float (seconds)
sema
(define morning 1736935800.0)   ; 2025-01-15 10:10:00 UTC
(define afternoon 1736943000.0) ; 2025-01-15 12:10:00 UTC

(time/diff afternoon morning)  ; =>  7200.0  (2 hours)
(time/diff morning afternoon)  ; => -7200.0  (negative — morning is earlier)
(time/diff morning morning)    ; =>  0.0

TIP

time/diff returns a signed value: positive when t1 > t2, negative when t1 < t2. Use abs if you need the absolute elapsed time regardless of order.

Delay

sleep

Pause execution for a given number of milliseconds. Returns nil.

sema
(sleep milliseconds) ; => nil
sema
(sleep 1000)  ; sleep for 1 second
(sleep 500)   ; sleep for 500ms
(sleep 0)     ; yield (no-op pause)

Note that sleep takes milliseconds (not seconds), unlike the time/ functions which work in seconds.

strftime Format Directives

The time/format and time/parse functions use chrono strftime format directives. Here are the most common ones:

Date

DirectiveDescriptionExample
%YFour-digit year2025
%mMonth (zero-padded, 01–12)01
%dDay of month (zero-padded, 01–31)15
%eDay of month (space-padded)15
%BFull month nameJanuary
%bAbbreviated month nameJan
%AFull weekday nameWednesday
%aAbbreviated weekday nameWed
%uDay of week (1=Monday, 7=Sunday)3
%jDay of year (001–366)015
%FISO 8601 date (%Y-%m-%d)2025-01-15

Time

DirectiveDescriptionExample
%HHour, 24-hour (zero-padded, 00–23)12
%IHour, 12-hour (zero-padded, 01–12)12
%MMinute (zero-padded, 00–59)10
%SSecond (zero-padded, 00–59)00
%pAM/PMPM
%TTime (%H:%M:%S)12:10:00
%RShort time (%H:%M)12:10

Combined & Special

DirectiveDescriptionExample
%cLocale date and timeWed Jan 15 12:10:00 2025
%sUnix timestamp (seconds)1736943000
%ZTimezone abbreviationUTC
%%Literal %%

Common Patterns

Measuring elapsed time

sema
(define start (time/now))
;; ... do some work ...
(define end (time/now))
(define elapsed (time/diff end start))
(println (format "Took ~a seconds" elapsed))

ISO 8601 formatting

sema
(define ts (time/now))
(time/format ts "%Y-%m-%dT%H:%M:%SZ")  ; => "2025-01-15T12:10:00Z"
(time/format ts "%F")                    ; => "2025-01-15" (date only)

Calculating "N days ago"

sema
(define now (time/now))
(define one-week-ago (time/add now (* -7 86400)))
(define thirty-days-ago (time/add now (* -30 86400)))

(println "One week ago: " (time/format one-week-ago "%Y-%m-%d"))

Formatting for display

sema
(define ts (time/now))
(time/format ts "%A, %B %d, %Y")    ; => "Wednesday, January 15, 2025"
(time/format ts "%I:%M %p")          ; => "12:10 PM"
(time/format ts "%b %d at %H:%M")   ; => "Jan 15 at 12:10"

Checking the day of the week

sema
(define parts (time/date-parts (time/now)))
(define day (get parts :weekday))

(if (or (= day "Saturday") (= day "Sunday"))
  (println "It's the weekend!")
  (println "It's a weekday."))

Computing duration between dates

sema
(define start (time/parse "2025-01-01 00:00:00" "%Y-%m-%d %H:%M:%S"))
(define end   (time/parse "2025-03-15 00:00:00" "%Y-%m-%d %H:%M:%S"))

(define diff-seconds (time/diff end start))
(define diff-days (/ diff-seconds 86400))
(println (format "~a days between dates" diff-days))

Edge Cases

Unix epoch

sema
(time/format 0.0 "%Y-%m-%d %H:%M:%S")  ; => "1970-01-01 00:00:00"
(time/date-parts 0.0)
; => {:day 1 :hour 0 :minute 0 :month 1 :second 0 :weekday "Thursday" :year 1970}

Negative timestamps (dates before 1970)

sema
(time/format -86400.0 "%Y-%m-%d")  ; => "1969-12-31"
(time/format -31536000.0 "%Y-%m-%d")  ; => "1969-01-01"

Sub-second precision

time/now returns millisecond fractional precision. time/add and time/diff preserve fractional seconds. However, time/parse returns whole seconds only.

sema
(define ts (time/add 1736943000.0 0.5))   ; add 500ms
(time/diff ts 1736943000.0)               ; => 0.5