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