1 /*******************************************************************************
2 
3         Copyright:
4             Copyright (c) 2007 Kris Bell.
5             Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
6             All rights reserved.
7 
8         License:
9             Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
10             See LICENSE_TANGO.txt for details.
11 
12         Version: Feb 2007: Initial release
13 
14         Authors: Kris
15 
16 *******************************************************************************/
17 
18 module ocean.time.Clock;
19 
20 public  import ocean.time.Time;
21 
22 import ocean.sys.Common;
23 
24 import ocean.core.ExceptionDefinitions;
25 
26 version(UnitTest) import ocean.core.Test;
27 
28 /******************************************************************************
29 
30         Exposes UTC time relative to Jan 1st, 1 AD. These values are
31         based upon a clock-tick of 100ns, giving them a span of greater
32         than 10,000 years. These units of time are the foundation of most
33         time and date functionality in Tango contributors.
34 
35         Interval is another type of time period, used for measuring a
36         much shorter duration; typically used for timeout periods and
37         for high-resolution timers. These intervals are measured in
38         units of 1 second, and support fractional units (0.001 = 1ms).
39 
40 *******************************************************************************/
41 
42 struct Clock
43 {
44         /// Time at which the program started
45         private static Time start_time_;
46 
47         /// Returns: Time at which the application started
48         public static Time startTime ()
49         {
50             return start_time_;
51         }
52 
53         static this ()
54         {
55             start_time_ = Clock.now;
56         }
57 
58         // copied from Gregorian.  Used while we rely on OS for toDate.
59         package static uint[] DaysToMonthCommon = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
60         package static void setDoy(ref DateTime dt)
61         {
62             uint doy = dt.date.day + DaysToMonthCommon[dt.date.month - 1];
63             uint year = dt.date.year;
64 
65             if(dt.date.month > 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)))
66                 doy++;
67 
68             dt.date.doy = doy;
69         }
70 
71 
72     /***************************************************************************
73 
74         Returns:
75             the current time as UTC since the epoch
76 
77     ***************************************************************************/
78 
79     static Time now ()
80     {
81         timeval tv = void;
82         if (gettimeofday (&tv, null))
83             throw new PlatformException ("Clock.now :: Posix timer is not available");
84 
85         return convert (tv);
86     }
87 
88     /***************************************************************************
89 
90         Set Date fields to represent the current time.
91 
92     ***************************************************************************/
93 
94     static DateTime toDate ()
95     {
96         return toDate(now);
97     }
98 
99     /***************************************************************************
100 
101         Set fields to represent the provided UTC time.
102 
103         Note that the conversion is limited by the underlying OS, and will fail
104         to operate correctly with Time values beyond the domain, which is
105         01-01-1970 on Linux.
106         Date is limited to millisecond accuracy at best.
107 
108     ***************************************************************************/
109 
110     static DateTime toDate (Time time)
111     {
112         DateTime dt = void;
113         auto timeval = convert (time);
114         dt.time.millis = cast(uint) (timeval.tv_usec / 1000);
115 
116         tm t = void;
117         gmtime_r (&timeval.tv_sec, &t);
118 
119         dt.date.year    = t.tm_year + 1900;
120         dt.date.month   = t.tm_mon + 1;
121         dt.date.day     = t.tm_mday;
122         dt.date.dow     = t.tm_wday;
123         dt.date.era     = 0;
124         dt.time.hours   = t.tm_hour;
125         dt.time.minutes = t.tm_min;
126         dt.time.seconds = t.tm_sec;
127 
128         // Calculate the day-of-year
129         setDoy(dt);
130 
131         return dt;
132     }
133 
134     /***************************************************************************
135 
136         Convert Date fields to Time
137 
138         Note that the conversion is limited by the underlying OS, and will fail
139         to operate correctly with Time values beyond the domain, which is
140         01-01-1970 on Linux.
141         Date is limited to millisecond accuracy at best.
142 
143     ***************************************************************************/
144 
145     static Time fromDate (ref DateTime dt)
146     {
147         tm t = void;
148 
149         t.tm_year = dt.date.year - 1900;
150         t.tm_mon  = dt.date.month - 1;
151         t.tm_mday = dt.date.day;
152         t.tm_hour = dt.time.hours;
153         t.tm_min  = dt.time.minutes;
154         t.tm_sec  = dt.time.seconds;
155 
156         auto seconds = timegm (&t);
157         return Time.epoch1970 +
158             TimeSpan.fromSeconds(seconds) +
159             TimeSpan.fromMillis(dt.time.millis);
160     }
161 
162     /***************************************************************************
163 
164         Convert timeval to a Time
165 
166     ***************************************************************************/
167 
168     package static Time convert (ref timeval tv)
169     {
170         return Time.epoch1970 +
171             TimeSpan.fromSeconds(tv.tv_sec) +
172             TimeSpan.fromMicros(tv.tv_usec);
173     }
174 
175     /***************************************************************************
176 
177         Convert Time to a timeval
178 
179     ***************************************************************************/
180 
181     package static timeval convert (Time time)
182     {
183         timeval tv = void;
184 
185         TimeSpan span = time - time.epoch1970;
186         assert (span >= TimeSpan.zero);
187         tv.tv_sec  = cast(typeof(tv.tv_sec)) span.seconds;
188         tv.tv_usec = cast(typeof(tv.tv_usec)) (span.micros % 1_000_000L);
189         return tv;
190     }
191 }
192 
193 
194 
195 unittest
196 {
197     auto time = Clock.now;
198     auto clock=Clock.convert(time);
199     test (Clock.convert(clock) is time);
200 
201     time -= TimeSpan(time.ticks % TimeSpan.TicksPerSecond);
202     auto date = Clock.toDate(time);
203 
204     test (time is Clock.fromDate(date));
205 }