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.
2variablesare used to hold the resultsusing 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
.shortvariants ofut>sands>dtuse a known starting point stored in_last_esecand_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
...
;
References¶
The Code¶
1\ 2014-10-13 ew
2\
3\ Written in 2014-2017 by Erich Wälde <erich.waelde@forth-ev.de>
4\
5\ To the extent possible under law, the author(s) have dedicated
6\ all copyright and related and neighboring rights to this software
7\ to the public domain worldwide. This software is distributed
8\ without any warranty.
9\
10\ You should have received a copy of the CC0 Public Domain
11\ Dedication along with this software. If not, see
12\ <http://creativecommons.org/publicdomain/zero/1.0/>.
13\
14\ words
15\ leapyear? FIXME: there is an implementation in ewlib/timeup.fs as well
16\ __Epoch 1970, constant
17\ s>dt ( d:EpochSeconds -- sec min hour day month year )
18\ s>dt.short ( d:EpochSeconds -- sec min hour day month year )
19\ ut>s ( sec min hour day month year -- T/sec )
20\ ut>s.short ( sec min hour day month year -- T/sec )
21\
22\ internal use only
23\ 365+1 ( year -- 365|366 )
24\ years/mod
25\ years/mod.short
26\ __acc_days -- accumulated days of year at 1st of each month (0..11)
27\ months/mod
28
29\ #include m-star-slash.frt
30\ #include leap_year_q.fs
31
32&1970 constant __Epoch
33: 365+1 ( year -- 365|366 )
34 &365 swap leap_year? if 1+ then
35;
36: years/mod.short ( T/day -- years T/day' )
37 dup &365 u>= if \ -- T
38 _last_epoch @ swap \ -- year T
39 begin
40 over 365+1
41 -
42 swap 1+ swap \ -- T-365/6 year+1
43 over 365+1 \ -- year' T' 365
44 over swap \ -- year' T' T' 365
45 u>= 0= until
46 else
47 _last_epoch @ swap
48 then
49
50;
51: years/mod ( T/day -- years T/day' )
52 dup &365 u>= if \ -- T
53 __Epoch swap \ -- year T
54 begin
55 over 365+1
56 -
57 swap 1+ swap \ -- T-365/6 year+1
58 over 365+1 \ -- year' T' 365
59 over swap \ -- year' T' T' 365
60 u>= 0= until
61 else
62 __Epoch swap
63 then
64;
65create __acc_days 0 , &31 , &59 , &90 , &120 , &151 , &181 ,
66 &212 , &243 , &273 , &304 , &334 , &365 ,
67: months/mod ( year T/day -- year month T/day' )
68 dup 0= if
69 drop 1 1
70 else
71 &12 swap \ -- year month T
72 begin
73 over __acc_days + @i
74 \ -- year month T acc_days[month]
75 \ correct acc_days for leap year and months > 1 (January)
76 3 pick leap_year? 3 pick 1 > and if 1+ then
77 over over swap \ -- year month T acc_days[month] acc_days[month] T
78 u>
79 while \ -- year month T
80 drop swap 1- swap
81 \ -- year month-1 T
82 repeat \ -- year month' T acc_days[month']
83 - \ -- year month' T-acc_days[month']
84 swap 1+
85 swap 1+
86 then
87;
88
89: s>dt.short ( d:EpochSeconds -- sec min hour day month year )
90 _last_esec 2@ d-
91 &60 ud/mod \ -- sec T/min
92 &60 ud/mod \ -- sec min T/hour
93 &24 ud/mod \ -- sec min hour T/day
94 d>s
95 years/mod.short \ -- sec min hour year T/day
96 months/mod \ -- sec min hour year month day
97 swap \ -- sec min hour year day month
98 rot \ -- sec min hour day month year
99;
100
101: s>dt ( d:EpochSeconds -- sec min hour day month year )
102 &60 ud/mod \ -- sec T/min
103 &60 ud/mod \ -- sec min T/hour
104 &24 ud/mod \ -- sec min hour T/day
105 d>s
106 years/mod \ -- sec min hour year T/day
107 months/mod \ -- sec min hour year month day
108 swap \ -- sec min hour year day month
109 rot \ -- sec min hour day month year
110;
111
112: ut>s.short ( sec min hour day month year -- T/sec )
113 \ add start value T=0
114 0 over \ -- sec min hour day month year T=0 year
115 _last_epoch @ \ -- sec min hour day month year T year Epoch
116 ?do
117 i 365+1 +
118 loop \ -- sec min hour day month year T/days
119 2 pick 1- \ -- sec min hour day month year T/days month-1
120 __acc_days + @i \ -- sec min hour day month year T/days acc_days[month]
121 + \ -- sec min hour day month year T/days
122 swap \ -- sec min hour day month T/days year
123 leap_year? rot 2 > and if 1+ then
124 \ \ -- sec min hour day T/days
125 swap 1- + \ -- sec min hour T/days
126 s>d
127 24 1 m*/ rot s>d d+ \ -- sec min T/hours
128 60 1 m*/ rot s>d d+ \ -- sec T/minutes
129 60 1 m*/ rot s>d d+ \ -- T/sec
130 _last_esec 2@ d+ \ -- T/sec
131;
132
133: ut>s ( sec min hour day month year -- T/sec )
134 \ add start value T=0
135 0 over \ -- sec min hour day month year T=0 year
136 __Epoch \ -- sec min hour day month year T year Epoch
137 ?do
138 i 365+1 +
139 loop \ -- sec min hour day month year T/days
140 2 pick 1- \ -- sec min hour day month year T/days month-1
141 __acc_days + @i \ -- sec min hour day month year T/days acc_days[month]
142 + \ -- sec min hour day month year T/days
143 swap \ -- sec min hour day month T/days year
144 leap_year? rot 2 > and if 1+ then
145 \ \ -- sec min hour day T/days
146 swap 1- + \ -- sec min hour T/days
147 s>d
148 24 1 m*/ rot s>d d+ \ -- sec min T/hours
149 60 1 m*/ rot s>d d+ \ -- sec T/minutes
150 60 1 m*/ rot s>d d+ \ -- T/sec
151;