Swatch Internet Time

Swatch Internet Time or .beat time is a decimal time format without time zones. It was introduced by the Swatch corporation to promote their "Beat" watches. Some products released around the year 2000 have integrated support for .beat time, as does the programming language PHP.

Tcl module

dbohdan 2021-02-10: The following module can convert a clock seconds timestamp to .beat time and .beat time to seconds since midnight in UTC. It also works as a command line utility to display Swatch Internet Time. It is compatible with Jim Tcl.

#! /usr/bin/env tclsh
# An Internet Time (.beat time) and general decimal time library.
# Copyright (c) 2021 D. Bohdan.
# License: MIT.

namespace eval beat {
    variable version 0.5.0

    # [catch] for Jim Tcl compatibility.
    catch {
        namespace export format scan *-to-*
        namespace ensemble create
    }
}


# Epoch time in seconds -> .beats.
proc beat::format t {
    # Add an hour to go from UTC to UTC+1 (CET).
    seconds-to-decimal [expr { ($t + 3600) % 86400 }]
}


# .beats -> seconds since midnight in UTC.
proc beat::scan beats {
    expr { ([decimal-to-seconds $beats] - 3600) % 86400 }
}


proc beat::seconds-to-decimal {s {unitsPerDay 1000}} {
    expr { int($s * $unitsPerDay / 86400.0) }
}


proc beat::decimal-to-seconds {dec {unitsPerDay 1000}} {
    expr { int(ceil($dec * 86400.0 / $unitsPerDay)) }
}


proc beat::test {command reference {maxDiff 0}} {
    upvar 1 errors errors

    set within [expr { $maxDiff == 0 ? {} : " within $maxDiff" }]

    set value [uplevel 1 $command]

    if {abs($value - $reference) > $maxDiff} {
        lappend errors "\[$command\] -> $value instead of $reference$within"
    }
}


proc beat::run-tests {} {
    set ref {
        1000000000 115
        1100000000 523
        1200000000 930
        1300000000 337
        1400000000 745
        1500000000 152
        1600000000 560
        1700000000 967
        1800000000 375
        1900000000 782
        2000000000 189
        1612973550 717
    }

    set errors {}

    dict for {timestamp beats} $ref {
        test [list format $timestamp] $beats

        test [list scan $beats] [expr { $timestamp % 86400 }] 86
    }

    test [list seconds-to-decimal 48400 1000] 560
    test [list seconds-to-decimal 48400 200] 112
    test [list seconds-to-decimal 48400 100] 56
    test [list seconds-to-decimal 48400 20] 11
    test [list seconds-to-decimal 48400 10] 5

    test [list decimal-to-seconds 560 1000] 48384
    test [list decimal-to-seconds 112 200] 48384
    test [list decimal-to-seconds 56 100] 48384
    test [list decimal-to-seconds 11 20] 47520
    test [list decimal-to-seconds 5 10] 43200

    for {set i 0} {$i < 1000} {incr i} {
        test "seconds-to-decimal \[decimal-to-seconds $i\]" $i
    }

    for {set i 0} {$i < 86400} {incr i} {
        test "decimal-to-seconds \[seconds-to-decimal $i\]" $i 86
    }

    if {$errors ne {}} {
        error "tests failed:\n[join $errors \n]"
    }
}


proc beat::usage {} {
    puts stderr "usage: [file tail [info script]] \[(timestamp|\"test\")\]"
}


proc beat::main argv {
    switch [llength $argv] {
        0 {
            set timestamp [clock seconds]
        }
        1 {
            if {$argv eq {test}} {
                run-tests
                exit 0
            }

            set timestamp $argv
            if {![string is integer -strict $timestamp]} {
                usage
                exit 1
            }
        }
        default {
            usage
            exit 1
        }
    }


    puts [format $timestamp]
}


# If this is the main script...
if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} {
    beat::main $argv
}

See also