Unix Epoch Seconds, revisited¶
Date: | 2017-09-01 |
---|
Intro¶
Unix Epoch Seconds are a commonly used time scale with computers. They start at 1970-01-01 00:00:00h UTC and increase eversince. At the time of this writing they have surpassed 1503689470. I decided to use them on the clocks to simplify handling of timezones. A time zone can then be implemented as an offset in seconds. This may not be the most simple method, but once working conversion routines are available, they can be used easily.
Unix Epoch Seconds have no concept of leap seconds, so this time scale is a little distorted occasionally.
Unix Epoch Seconds have traditionally been implemented as signed 32bit integer values. So there will be an instant, where this counter wraps to negative values — this is the cause of the so called year-2038 problem . My implementation uses an unsigned 32bit counter, so the problem moves out to 2106. In Detail:
N Epoch seconds Date_Time 2^31-1 2147483647 2038-01-19_03:14:07 UTC 2^32-1 4294967295 2106-02-07_06:28:15 UTC
Unix Epoch Seconds make for geeky displays, either in decimal (the value 1500000000 was reached not so long ago at 2017-07-14 02:40:00 UTC) or in binary: you can see the year 2038 rollover coming!
Design Decisions¶
- the conversion routines are stupid, they count days accumulated over the full years since 1970, and add days, hours, minutes, and seconds along the way.
2variables
are used to hold the results- using them unsigned (which is just a decision of the programmer) gets rid of the year-2038 problem. Whether my hardware will see the year-2106 problem seems rather less likely.
This code has been published before on this site ( Date/Time to unix time and back ) including some test cases.
However, by its very nature a clock is indicating increasing instances in time, so calculating the same time spans over and over again seems like a bit of a waste. Thus I added some shortcuts.
- the
.short
variants ofut>s
ands>dt
use a known starting point stored in_last_esec
and_last_epoch
. This point can be changed at the beginning of a new year, for example. NB the functions will fail if the point to be converted is before_last_esec
.
Code Details¶
s>dt.short ( d:EpochSeconds -- sec min hour day month year )
ut>s.short ( sec min hour day month year -- d:EpochSeconds )
Putting it all together¶
The 2variable EsecOffset
holds the offset of the current time zone
in seconds. The offset is applied to the Esec
counter and then
converted to HMS counters and displayed:
variable _last_epoch
2variable _last_esec
#2017 Evalue EE_last_epoch
#1483228800. 2Evalue EE_last_esec
#include epochseconds.fs
2variable Esec
: ++Esec ( -- ) Esec 2@ 1. d+ Esec 2! ;
: .Esec ( -- ) Esec 2@ ud. ;
...
: local.dt ( -- S M H d m Y )
Esec 2@ EsecOffset 2@ d+ s>dt.short
;
: cd.localtime
local.dt \ -- S M H d m Y
drop drop drop \ -- S M H
rot drop swap \ -- H M
>r #10 /mod swap \ -- H.10 H.1
r> #10 /mod swap \ -- H.10 H.1 M.10 M.1
#4 type.7seg \ --
;
: job.min ...
cd.localtime
;
: init
...
0. Esec 2!
EE_last_epoch _last_epoch !
EE_last_esec _last_esec 2!
#3600. EsecOffset 2! \ UTC+1
...
;
The Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | \ 2014-10-13 ew
\
\ Written in 2014-2017 by Erich Wälde <erich.waelde@forth-ev.de>
\
\ To the extent possible under law, the author(s) have dedicated
\ all copyright and related and neighboring rights to this software
\ to the public domain worldwide. This software is distributed
\ without any warranty.
\
\ You should have received a copy of the CC0 Public Domain
\ Dedication along with this software. If not, see
\ <http://creativecommons.org/publicdomain/zero/1.0/>.
\
\ words
\ leapyear? FIXME: there is an implementation in ewlib/timeup.fs as well
\ __Epoch 1970, constant
\ s>dt ( d:EpochSeconds -- sec min hour day month year )
\ s>dt.short ( d:EpochSeconds -- sec min hour day month year )
\ ut>s ( sec min hour day month year -- T/sec )
\ ut>s.short ( sec min hour day month year -- T/sec )
\
\ internal use only
\ 365+1 ( year -- 365|366 )
\ years/mod
\ years/mod.short
\ __acc_days -- accumulated days of year at 1st of each month (0..11)
\ months/mod
\ #include m-star-slash.frt
\ #include leap_year_q.fs
&1970 constant __Epoch
: 365+1 ( year -- 365|366 )
&365 swap leap_year? if 1+ then
;
: years/mod.short ( T/day -- years T/day' )
dup &365 u>= if \ -- T
_last_epoch @ swap \ -- year T
begin
over 365+1
-
swap 1+ swap \ -- T-365/6 year+1
over 365+1 \ -- year' T' 365
over swap \ -- year' T' T' 365
u>= 0= until
else
_last_epoch @ swap
then
;
: years/mod ( T/day -- years T/day' )
dup &365 u>= if \ -- T
__Epoch swap \ -- year T
begin
over 365+1
-
swap 1+ swap \ -- T-365/6 year+1
over 365+1 \ -- year' T' 365
over swap \ -- year' T' T' 365
u>= 0= until
else
__Epoch swap
then
;
create __acc_days 0 , &31 , &59 , &90 , &120 , &151 , &181 ,
&212 , &243 , &273 , &304 , &334 , &365 ,
: months/mod ( year T/day -- year month T/day' )
dup 0= if
drop 1 1
else
&12 swap \ -- year month T
begin
over __acc_days + @i
\ -- year month T acc_days[month]
\ correct acc_days for leap year and months > 1 (January)
3 pick leap_year? 3 pick 1 > and if 1+ then
over over swap \ -- year month T acc_days[month] acc_days[month] T
u>
while \ -- year month T
drop swap 1- swap
\ -- year month-1 T
repeat \ -- year month' T acc_days[month']
- \ -- year month' T-acc_days[month']
swap 1+
swap 1+
then
;
: s>dt.short ( d:EpochSeconds -- sec min hour day month year )
_last_esec 2@ d-
&60 ud/mod \ -- sec T/min
&60 ud/mod \ -- sec min T/hour
&24 ud/mod \ -- sec min hour T/day
d>s
years/mod.short \ -- sec min hour year T/day
months/mod \ -- sec min hour year month day
swap \ -- sec min hour year day month
rot \ -- sec min hour day month year
;
: s>dt ( d:EpochSeconds -- sec min hour day month year )
&60 ud/mod \ -- sec T/min
&60 ud/mod \ -- sec min T/hour
&24 ud/mod \ -- sec min hour T/day
d>s
years/mod \ -- sec min hour year T/day
months/mod \ -- sec min hour year month day
swap \ -- sec min hour year day month
rot \ -- sec min hour day month year
;
: ut>s.short ( sec min hour day month year -- T/sec )
\ add start value T=0
0 over \ -- sec min hour day month year T=0 year
_last_epoch @ \ -- sec min hour day month year T year Epoch
?do
i 365+1 +
loop \ -- sec min hour day month year T/days
2 pick 1- \ -- sec min hour day month year T/days month-1
__acc_days + @i \ -- sec min hour day month year T/days acc_days[month]
+ \ -- sec min hour day month year T/days
swap \ -- sec min hour day month T/days year
leap_year? rot 2 > and if 1+ then
\ \ -- sec min hour day T/days
swap 1- + \ -- sec min hour T/days
s>d
24 1 m*/ rot s>d d+ \ -- sec min T/hours
60 1 m*/ rot s>d d+ \ -- sec T/minutes
60 1 m*/ rot s>d d+ \ -- T/sec
_last_esec 2@ d+ \ -- T/sec
;
: ut>s ( sec min hour day month year -- T/sec )
\ add start value T=0
0 over \ -- sec min hour day month year T=0 year
__Epoch \ -- sec min hour day month year T year Epoch
?do
i 365+1 +
loop \ -- sec min hour day month year T/days
2 pick 1- \ -- sec min hour day month year T/days month-1
__acc_days + @i \ -- sec min hour day month year T/days acc_days[month]
+ \ -- sec min hour day month year T/days
swap \ -- sec min hour day month T/days year
leap_year? rot 2 > and if 1+ then
\ \ -- sec min hour day T/days
swap 1- + \ -- sec min hour T/days
s>d
24 1 m*/ rot s>d d+ \ -- sec min T/hours
60 1 m*/ rot s>d d+ \ -- sec T/minutes
60 1 m*/ rot s>d d+ \ -- T/sec
;
|