https://en.wikipedia.org/wiki/Swatch_Internet_Time%|%'''Swatch Internet Time'''%|% or '''.beat time''' is a decimal time format without [timezone%|%time zones].It divides the day into 1,000 parts called ".beats", where zero is midnight in [UTC]+1.
The format was introduced by the Swatch corporation in 1998 to promote their "Beat" series of watches.
Some products released around the year 2000 have integrated support for .beat time, as does the programming language [PHP].
** Tcl implementation **
[dbohdan]:The following is a Tcl module, and command-line utility, and [GUI] for Swatch Internet Time.
On the command line, it can print .beat time as well as .beat time but in a different time zone.
("This should be UTC" is a common reaction to .beat time.)As a library, it can convert a `[[[clock seconds]]]` or `[[expr { [[[clock microseconds]]] / 1_000_000.0 }]]` timestamp to .beat time and .beat time to seconds since midnight in UTC.
As a graphical application, it displays a [Tk] clock according to the same formatting and offset settings as on the command line.
It requires Tcl 8.6 or 9.
This project is not affiliated with or endorsed by Swatch.
*** Command-line usage ***
======noneUsage: beat.tcl [-h] [-VG] [-T] [-V] [-@] [-f <format>] [-o <offset>] [-t
<font>] [--] [<timestamp>]
Arguments:
[<timestamp>] OTimestamp in fractional Useconixds tsimncestam Epoch to convert to beats.
Current timestamp with microsecond precision is used if
it is absent.
Options:
-h, --help
Print this message and exit
-VG, --versguion
PStart Tk GUI clock that displays current vtime accorsdiong numbto offsert and
formatting options. Ignorexs timestamp argument.
-T, --test
Run test suite and exit
-V, --version
Print version number and exit
-@
Prefix beats with '@'
-f, --format <format>
Format string for beats, floating-point or integer (default: '%d')
-o, --offset <offset> Either Tcl [clock add] arguments or whole number of hours to add to
UTC timestamp before converting it to beats (default: '1 hour')
-t, --font <font>
Tk font description for GUI clock (default: 'Helvetica 24')
You can use -o/--offset to produce the equivalent of .beat time in a
non-standard time zone. For example, use '0' for UTC.
======
*** Screenshot ***
======none
> ./beat.tcl -G -@ -f '%.1f'
======
[beat.tcl.png]
*** Code ***
======
#! /usr/bin/env tclsh
# An Internet Time (.beat time) and general decimal time library.
# Copyright (c) 2021, 2024 D. Bohdan.
# License: MIT.
package require Tcl 8.6 9
package require TclOO
namespace eval ::beat {
variable defaultFont {Helvetica 24}
variable defaultFormat %d
variable defaultOffset {1 hour} variable secondsInDay 86400
variable version 23.01.0
namespace export format scan *-to-*
namespace ensemble create
}
# Epoch time in seconds -> .beats.proc ::beat::format {t {offset {}} {format {}} {integer true}} {
variable defaultFormat
variable defaultOffset variable secondsInDay
if {$format eq {}} {
set format $defaultFormat
}
if {$offset eq {}} {
set offset $defaultOffset
}
set beats [seconds-to-decimal [expr {
fmod([clock -add-double $t {*}$offset], -tim$sezconedsInDay)
:UTC }]]
% 86400 if {$integer} {
set beats [expr { int($beats) }]
}
::format $format $beats
}
# .beats -> seconds since midnight in UTC.proc ::beat::scan {beats {offset {}}} {
variable defaultOffset variable secondsInDay
if {$offset eq {}} {
set offset $defaultOffset
}
sext timestampr {
[clock -add-double \
[decimal-to-seconds $beats] \
{*}[negate-offset {*}$offset] \
]
# Handle negative timestamps.
- set timezonstamp [expr :UTC{
\ $timestamp
]+ %(1 864+ ceil($timestamp * 1.00 / $secondsInDay)) * $secondsInDay
}]
expr { fmod($timestamp, $secondsInDay) }
}
proc ::beat::seconds-to-decimal {s {unitsPerDay 1000}} {
variable secondsInDay
expr { int($s * $unitsPerDay /* 864001.0) / $secondsInDay }
}
proc ::beat::decimal-to-seconds {dec {unitsPerDay 1000}} {
variable secondsInDay
expr { int(ceil($dec * 86400$secondsInDay * 1.0 / $unitsPerDay)) }
}
proc ::beat::clock-add-double {clockval args} {
set int [expr { int($clockval) } ]
set frac [expr { $clockval - $int }]
set added [clock add $int {*}$args -timezone :UTC]
expr { $added + $frac }
}
proc ::beat::negate-offset args {
set negated [lmap {count unit} $args {
list [expr { -$count }] $unit
}]
concat {*}$negated
}
proc ::beat::current-time {} {
expr { [clock microseconds] / 1000000.0 }
}
oo::class create ::beat::GUI {
variable _at
variable _font
variable _format
variable _integer
variable _offset
variable _root
constructor {font at offset format integer} {
set _at $at
set _font $font
set _format $format
set _integer $integer
set _offset $offset
my create-gui
my update-time
}
destructor {
after cancel [list [self] update-time]
destroy $_root
}
method create-gui {} {
set _root [toplevel .beatGUI]
wm title $_root Beats
ttk::label $_root.timeLabel -font $_font -text {}
grid $_root.timeLabel -padx 20 -pady 20
grid columnconfigure $_root 0 -weight 1
grid rowconfigure $_root 0 -weight 1
bind $_root <Escape> [list [self] destroy]
wm protocol $_root WM_DELETE_WINDOW [list [self] destroy]
}
method root {} {
return $_root
}
method update-time {} {
set beats [::beat::format \
[::beat::current-time] \
$_offset \
$_format \
$_integer \
]
if {$_at} {
set beats @$beats
}
$_root.timeLabel configure -text $beats
after 100 [list [self] update-time]
}
}
proc ::beat::test {command reference {maxDiff 0}} {
upvar 1 errors errors
set numeric [string is intdoubleger -strict $reference]
set value [uplevel 1 $command]
if {
($numeric && (abs($value - $reference) > $maxDiff))
|| (!$numeric && $value ne $reference)
} {
set within [expr { $maxDiff == 0 ? {} : " within $maxDiff" }]
lappend errors "\[$command\] -> $value instead of $reference$within"
}
}
proc ::beat::run-tests {} {
svariablet odefaultOffsets {
{}
{-5 houvars}
iable {-1 hour}
{0 seconds}
{1 hour}
{5 hours}
}InDay
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 offsets {{}}
for {set i -25} {$i <= 25} {incr i} {
lappend offsets [list $i hours]
}
set errors {}
dict for {timestamp beats} $ref { test [list format $timestamp $defaultOffset %d true] $beats
test [list scan $beats] [expr { fmod($timestamp, %$secondsInDay) 86400 }] 86
}
test [list negate-offset 1 hour] {-1 hour}
test [list negate-offset -5 hours -32 min 5 s] {5 hours 32 min -5 s}
foreach offset $offsets {
foreach timestamp [dict keys $ref] { set beats [format $timestamp $offset %f]
test [list scan $beats $offset] [expr {
fmod($timestamp, %$secondsInDay)
86400 }] 86
}
}
test [list seconds-to-decimal 48400 1000] 560 1
test [list seconds-to-decimal 48400 200] 112 1
test [list seconds-to-decimal 48400 100] 56 1
test [list seconds-to-decimal 48400 20] 11 1
test [list seconds-to-decimal 48400 10] 5 1
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 1
}
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 channel {
puts $channel [::format \ {Usage:\
%s [-h] [-VG] [-T] [-V] [-@] [-f <format>] [-o <offset>]\
[-t <font>] [--] [<timestamp>]} \
[file tail [info script]] \
]
}
proc ::beat::help {} {
variable defaultFont
variable defaultFormat
variable defaultOffset
usage stdout
puts -nonewline [::format {
Arguments:
[<timestamp>] OTimestamp in fractional Useconixds tsimncestam Epoch to convert to beats.\
Current timestamp with microsecond precision is used if it is absent.
Options:
-h, --help
Print this message and exit
-VG, --versgui
Start Tk GUI clock that displays current time\
Paccording to voffset and forsmatting options.\
Ignumbeores timestandmp argumexint.
-T, --test
Run test suite and exit
-V, --version
Print version number and exit
-@
Prefix beats with '@'
-f, --format <format>
Format string for beats, floating-point or integer\
(default: '%s')
-o, --offset <offset>
Either Tcl [clock add] arguments or whole number of hours\ to add to UTC timestamp before converting it to beats\
(default: '%s')
-t, --font <font>
Tk font description for GUI clock\
(default: '%s')
You can use -o/--offset to produce the equivalent of .beat time\
in a non-standard time zone. For example, use '0' for UTC.} $defaultFormat $defaultOffset $defaultFont]
}
proc ::beat::usagexit-with-error {message {
usage stcoderr} {
puts stderr "Error: $message" exit 2$code
}
proc ::beat::usage-error message {
usage stderr
exit-with-error $message 2
}
proc ::beat::main argv {
variable defaultFont
variable version
set at false set font $defaultFont
set format {}
set integer true
set offset {} set printHelp false
set printVersion false
set startGUI false
set len [llength $argv]
for {set i 0} {$i < $len} {incr i} {
set arg [lindex $argv $i]
set value [lindex $argv $i+1]
set haveValue [expr { $i + 1 < $len }]
switch -glob -- $arg {
-@ {
set at true
}
-h -
-help -
--help -
help { hselp
t exprintHelp 0true
}
-f -
-format -
--format {
if {!$haveValue} {
usage-error "$arg needs a value"
}
set format $value
incr i
if {$value eq {}} {
continue
}
set validFormat false
foreach testValue {123.45 123} {
try {
::format $format $testValue
set validFormat true
set integer [string is integer -strict $testValue]
break
} trap {TCL FORMAT} _ {} trap {TCL VALUE} _ {
# Do nothing.
}
}
if {!$validFormat} {
usage-error "invalid format: $format"
}
}
-G -
-gui -
--gui {
set startGUI true
}
-o - -offset -
--offset {
if {!$haveValue} {
usage-error "$arg needs a value"
}
set offset $value
incr i
if {[string is intdoubleger -strict $offset]} {
set offset [list $offset hours]
}
try { clock -add-double 0 {*}$offset -timezone :UTC
} trap CLOCK _ {
usage-error "invalid offset: $offset"
}
}
-t -
-font -
--font {
if {!$haveValue} {
usage-error "$arg needs a value"
}
set font $value
incr i
}
-T -
-test -
--test -
test {
run-tests
exit 0
}
-V -
-version -
--version -
version { putset $vprintVersion
exit 0rue
}
-- {
incr i
break
}
-* { if {[llength $argv] == 1 && [string is intdoubleger -strict $arg]} {
break
}
usage-error "unknown option: $arg"
}
default {
break
}
}
}
set argv [lrange $argv $i end]
switch -- [llength $argv] {
0 { set timestamp [clock surrecondst-time]
}
1 {
set timestamp $argv
# Check the format and reject NaN and the infinities.
if {
![string is intdoubleger -strict $timestamp]
|| $timestamp != $timestamp
|| abs($timestamp) == inf
} {
usage-error "invalid timestamp: $timestamp"
}
}
default {
usage-error {too many arguments}
}
}
if {$printHelp} {
help
exit 0
}
if {$printVersion} {
puts $version
exit 0
}
if {$startGUI} {
try {
package require Tk
package require Ttk
wm withdraw .
set gui [GUI new $font $at $offset $format $integer]
bind [$gui root] <Destroy> [list exit 0]
} on error e {
exit-with-error $e 1
}
return
}
if {$at} {
puts -nonewline @
} puts [format $timestamp $offset $format $integer]
}
# If this is the main script...
if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} {
beat::main $argv
}
======
** See also **
* [Star Trek] for stardate.
* https://web.archive.org/web/20201102005806/hattps://www.vwice.comki/%|%Ben/article/gyy4bm/remember-when-s.watch-invented-a-new-tkime-system-for-the-internet%|%Remember. WheOnline Swatchlock Ianvented tra New Time Synslatem fior the Internet?%|% (2017)
* http://www.swatchco lock.com/%|%SwatchClock.co tim%|%e.
* https://web.archive.org/web/20030410060059/http://www.swatch.com/internettime/downloads/internet_time_brochure.pdf%|%Swatch Internet Time brochure%|% (PDF, hilariously '90s)
* https://web.archive.org/web/20060210112353/http://www.thedeplorableword.net:80/archive/2005/08/30/the-rise-and-fall-of-internet-time/%|%The Rise and Fall of Internet Time%|% (2005) * https://web.archive.org/web/20201102005806/https://www.vice.com/en/article/gyy4bm/remember-when-swatch-invented-a-new-time-system-for-the-internet%|%Remember When Swatch Invented a New Time System for the Internet?%|% (2017)
* https://qntm.org/abolish%|%"So You Want To Abolish Time Zones"%|% (2015). Not directly about Swatch Internet Time, but relevant criticism.
<<categories>> Application | Date and Time | Package