forked from dlang/dlang.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
intro-to-datetime.dd
793 lines (715 loc) · 42.4 KB
/
intro-to-datetime.dd
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
Ddoc
$(D_S $(TITLE),
<img src="$(ROOT_DIR)images/dman-time.jpg" border=0 align=right alt="Clock within D Man" height=200>
$(P $(I by Jonathan M Davis))
$(H2 Introduction)
$(P In dmd 2.052, the module $(STD_DATETIME) was introduced. It will be
replacing $(STD_DATE) entirely. As such, $(STD_DATE) is currently
scheduled for deprecation. At a later date it will be deprecated
(at which point, you'll have to compile with -d for it to work rather
than simply having the compiler complain when you use it), and
eventually it will be fully removed from Phobos. What this means is that
all new code should be written to use $(STD_DATETIME) and that any code
which currently uses $(STD_DATE) is going to need to be refactored to
use $(STD_DATETIME) (unless you want to copy $(STD_DATE) to your own
code and continue to use it as a non-Phobos module). This article
attempts to familiarize you with $(STD_DATETIME) as well as give some
advice on how to migrate code from $(STD_DATE) to $(STD_DATETIME) for
those who have been using $(STD_DATE).)
$(P $(STD_DATE) is essentially a C-based solution for dates and times. It
uses $(D d_time) to hold time where $(D d_time) is a 64-bit integral
value holding the number of milliseconds which have passed since
midnight, January 1st, 1970 A.D. in UTC. C, on the other hand, uses
$(D time_t) to hold time where $(D time_t) is an integral value holding
the number of seconds which have passed since midnight, January 1st,
1970 A.D. in UTC. Its size varies from architecture to architecture
(typically 32 bits on a 32-bit machine and 64 bits on a 64-bit machine,
but it varies with the OS and compiler). The exact set of functions that
$(STD_DATE) provides for using with $(D d_time) aren't the same as what
C provides for using with $(D time_t), but their representation of time
is virtually the same.)
$(P $(STD_DATETIME), on the other hand, is very much an object-oriented
solution, and it's not C-based at all. Rather, its API is based on
$(HTTP www.boost.org/doc/libs/release/doc/html/date_time.html, Boost)'s
types for handling dates and times (though they're far from identical).
So, it's a bit of a paradigm shift to move from $(STD_DATE) to
$(STD_DATETIME). Don't expect much to be the same between the two.
However, $(STD_DATETIME) is not plagued with the same bugs that
$(STD_DATE) is plagued with ($(STD_DATE) being quite buggy in general),
and it provides much more functionality than $(STD_DATE) does. So, in
the long run at least, dealing with $(STD_DATETIME) should be much more
pleasant - though migration is likely to present a bit of a hurdle in
the short term.)
$(H2 Basic Concepts of std.datetime)
$(P Most things in $(STD_DATETIME) are based on and/or use three concepts:)
$(UL
$(LI $(B Duration): A duration of time with units. e.g. 4 days or 72
seconds.)
$(LI $(B Time Point): A specific point in time. e.g. 21:00 or May 7th,
2013.)
$(LI $(B Time Interval): A fixed period of time. e.g. [02:44 -
12:22$(RPAREN) or [February 4th, 1922 - January 17th,
1955$(RPAREN).)
)
$(H3 Durations)
$(P The duration types can actually be found in $(CORE_TIME). They are
$(DURATION) and $(TICK_DURATION). $(TICK_DURATION) is intended for
precision timing and is used primarily with $(REF_SHORT StopWatch, std,datetime)
and the benchmarking functions found in $(STD_DATETIME) - such as
$(REF_SHORT benchmark, std,datetime) - and you're unlikely to use it outside of
using them. $(DURATION), on the other hand, you're likely to use quite a
bit.)
$(P $(DURATION) holds its time internally as hecto-nanoseconds (100 ns), so
that's its maximum precision. It has property functions for both
returning the duration of time truncated to a particular unit (such as
the $(D days) and $(D seconds) property functions) as well as a function
for returning the total number of a particular unit in that $(DURATION)
(the $(D total) function).)
$(P Generally, a $(DURATION) is created in one of two ways: by subtracting
two time points or with the $(REF dur, core,time) function. So, for
instance, if you subtracted a time point which represented 17:02 from
a time point which represented 6:07, you'd get a $(DURATION) which
represented 10 hours and 55 minutes. Or, if you wanted to create a
$(DURATION) directly, then you'd use the $(D dur) function and make
a call like $(D dur!"hours"(17)) or $(D dur!"seconds"(234)).)
--------------------
auto duration = TimeOfDay(17, 2) - TimeOfDay(6, 7);
assert(duration == dur!"hours"(10) + dur!"minutes"(55));
assert(duration.hours == 10);
assert(duration.minutes == 55);
assert(duration.total!"hours"() == 10);
assert(duration.total!"minutes"() == 655);
assert(duration.total!"hnsecs"() == 393_000_000_000);
--------------------
$(P Like any number, $(DURATION)s can be added together or
subtracted from. However, unlike a naked number, they have units
associated with them and will handle the appropriate conversions.
Also, it should be noted that the various functions in druntime and
Phobos which take a duration of time take an actual $(DURATION) rather
than a naked number (most currently take both, though the versions which
take a naked number are going to be deprecated). For instance,
$(REF sleep, core,thread) takes a $(DURATION), as does
$(REF receiveTimeout, std,concurrency). So, durations are used outside
of just interacting with $(CORE_TIME) and $(STD_DATETIME).)
$(P One particular thing to note here is how both $(D dur) and $(D total)
take a string representing the units of time to be used. This is an
idiom used throughout $(CORE_TIME) and $(STD_DATETIME). The possible
units are $(D "years"), $(D "months"), $(D "weeks"), $(D "days"),
$(D "hours"), $(D "minutes"), $(D "seconds"), $(D "msecs"),
$(D "usecs"), $(D "hnsecs"), and $(D "nsecs"). It should be noted
however that very few functions take $(D "nsecs"), because nothing in
$(STD_DATETIME), and very little in $(CORE_TIME), has precision
greater than hnsecs (100 ns). Also, a number of functions (such as
$(REF dur, core,time)) do not take $(D "years") or $(D "months"),
because it is not possible to convert between years or months and
smaller units without a specific date. So, while you can add a
$(DURATION) to a time point, if you want to add years or months to one,
you must use a separate function (such as $(D add)) to do that - and
those $(I will) take $(D "years") and $(D "months").)
$(H3 Time Points)
$(P $(STD_DATETIME) has 4 types which represent time points.)
$(UL
$(LI $(DATE))
$(LI $(TIMEOFDAY))
$(LI $(DATETIME))
$(LI $(SYSTIME))
)
$(P A $(DATE) represents a date and holds its year, month, and day as
separate values internally. A $(TIMEOFDAY) represents a time of day,
00:00:00 - 23:59:59, and holds its hour, minute, and second as separate
values internally. A $(DATETIME) represents a date and time and holds
its values as a $(DATE) and $(TIMEOFDAY) internally. None of these types
have any concept of time zone. They represent generic dates and/or times
and are best-suited for cases where you need a date and/or time but
don't care about time zone. Also, because they hold their values
separated internally, those values don't have to be calculated every
time that you ask for them.)
--------------------
auto date = Date(1992, 12, 27);
auto tod = TimeOfDay(7, 0, 22);
auto dateTime = DateTime(1992, 12, 27, 7, 0, 22);
assert(date == dateTime.date);
assert(tod == dateTime.timeOfDay);
--------------------
$(P A $(SYSTIME), however, is an entirely different beast. It represents
a date and time - similar to $(DATETIME) - but it goes to hnsec
precision instead of only second precision, and it incorporates the
concept of time zone. Its time is held internally as a 64-bit integral
value which holds the number of hnsecs which have passed since midnight,
January 1st, 1 A.D. in UTC. It also has a $(TIMEZONE) object which it
uses to polymorphically adjust its UTC value to the appropriate time
zone when querying for values such as its year or hour.)
$(P $(SYSTIME) is the type which is used to interface with the system's
clock. When you ask for the current time, you get a $(SYSTIME). And
because it always holds its internal value in UTC, it never has problems
with DST or time zone changes. It has most of the functions that
$(DATETIME) has as well as a number of functions specific to it. It can
be cast to the other 3 time point types as well as be constructed from
them, but you do risk problems with DST when creating a $(SYSTIME) from
the other 3 time points unless you specifically create the $(SYSTIME)
with a $(TIMEZONE) which doesn't have DST (such as
$(REF UTC, std,datetime)), since when a time zone has DST, one hour of
the year does not exist, and another exists twice. You can also convert
to and from unix time, which is what you're dealing with in C with
$(D time_t).)
$(P The one other related type which I should mention at this point is
$(REF FracSec, core,time). It holds fractional seconds, and it is
what you get from a $(DURATION) or $(SYSTIME) when you specifically
ask for the fractional portion of the time.)
--------------------
auto st1 = Clock.currTime(); //Current time in local time.
auto st2 = Clock.currTime(UTC()); //Current time in UTC.
auto st3 = SysTime(DateTime(1992, 12, 27, 7, 0, 22), FracSec.from!"usecs"(5));
assert((cast(Date)st3) == Date(1992, 12, 27));
assert((cast(TimeOfDay)st3) == TimeOfDay(7, 0, 22));
assert((cast(DateTime)st3) == DateTime(1992, 12, 27, 7, 0, 22));
assert(st3.fracSec == FracSec.from!"hnsecs"(50));
--------------------
$(H3 Time Intervals)
$(P $(STD_DATE) has nothing to correspond to time intervals, so I won't
go over them in great detail. Essentially, they're constructed from
either two time points or a time point and a duration. $(INTERVAL)
is a finite time interval with two end points, whereas
$(POSINF_INTERVAL) is an infinite time interval starting at a specific
time point and going to positive infinity, and $(NEGINF_INTERVAL)
is an infinite time interval starting at negative infinity and going
to a specific time point. They have various operations for dealing
with intersections and the like. It is also possible to create ranges
over them if you want to operate on a range of time points. Take a
look at the documentation for more details.)
$(H2 Interfacing with C)
$(P Hopefully, you can do everything that you need to do using the types in
$(CORE_TIME) and $(STD_DATETIME), but if you do need to interface with C
code, then you can. C's $(D time_t) uses "unix time" (seconds since
midnight, January 1st, 1970 A.D. in UTC), whereas $(SYSTIME) uses what
it calls "std time" (hnsecs since midnight January 1st, 1 A.D. in UTC).
Translating between the two is fairly straightforward. To get a
$(D time_t) from a $(SYSTIME), simply call $(D toUnixTime) on the
$(SYSTIME). To convert the other way around, you first need to convert
a $(D time_t) to std time, then pass that value to $(SYSTIME)'s
constructor. And if you ever simply need a $(SYSTIME)'s std time for
any reason, then use its $(D stdTime) property.)
--------------------
time_t unixTime = core.stdc.time.time(null);
auto stdTime = unixTimeToStdTime(unixTime);
auto st = SysTime(stdTime);
assert(unixTime == st.toUnixTime());
assert(stdTime == st.stdTime);
--------------------
$(P Hecto-nanoseconds were chosen as the internal representation of
$(DURATION) and $(SYSTIME), because that is the highest precision that
you can use with a 64-bit integer and still cover a reasonable amount
of time ($(SYSTIME) covers from around 29,000 B.C. to around 29,000
A.D.). It also happens to be the same internal representation that
C# uses, so if you need to interface with C# for any reason, converting
between its representation of time and $(STD_DATETIME)'s representation
is extremely easy, since no conversion is necessary.
$(HTTP msdn.microsoft.com/en-us/library/system.datetime.aspx,
C#'s $(D DateTime)) uses both the same units and epoch for its internal
representation (which it calls $(D Ticks)) as $(SYSTIME), though unlike
$(SYSTIME), it doesn't work with negative values (which would be B.C.)
and doesn't go past the end of 9,999 A.D. Most programs are unlikely to
care about values outside that range however. Regardless, hnsecs make
the most sense for $(STD_DATETIME), which tries to have the highest
precision that it reasonably can, so that's why they were picked.)
$(H2 Recommendations on Using std.datetime)
$(P Whether $(DATE), $(TIMEOFDAY), $(DATETIME), or $(SYSTIME) is more
appropriate in a particular situation depends very much on that
situation. $(DATE), $(TIMEOFDAY), and $(DATETIME) generally make the
most sense when you're dealing with generic dates and times that have
nothing to do with time zones, but if you're dealing with time zones
at all or are dealing with anything which needs to worry about DST,
you should use $(SYSTIME). Because it keeps its time internally in UTC,
it avoids problems with DST. And while it does have a time zone
component, it defaults to $(REF LocalTime, std,datetime) (which
is the time zone type for the local time of the system), so you don't
generally have to deal directly with time zones if you don't want to.)
$(P If you $(I do) want to deal with time zones, then the time zone types
in $(STD_DATETIME) are $(LOCALTIME), $(UTC), $(SIMPLETZ), $(POSIXTZ),
and $(WINDOWSTZ) - or if for some reason, they don't do what you need,
you can always create your own time zone class derived from $(TIMEZONE).
That's unlikely to be necessary, however (and if you think that you
have come up with such a class which would be generally useful, please
bring it up in the
$(HTTP www.digitalmars.com/NewsGroup.html, digitalmars.D newsgroup),
since if it's truly generally useful, we may want some version of it in
$(STD_DATETIME)). Read their documentation for more details. Most
applications shouldn't have to worry about time zones though, beyond
perhaps using $(UTC) instead of $(LOCALTIME) in some cases.)
$(P When it comes to saving a time point to disk or a database or something
similar, I would generally recommend using the $(D toISOString) or
$(D toISOExtString) functions, since both are standard ISO formats
for date-time strings ($(D toISOExtString) is likely better in the
general case, since it's more humanly readable, but they're both
standard). You can then use $(D fromISOString) or $(D fromISOExtString)
to recreate the appropriate time type later. $(D toString) uses the
$(D toSimpleString) function, which is an invention of Boost and is
somewhat more humanly readable, but it isn't standard, so you probably
shouldn't use it for saving time point values.)
--------------------
auto dateTime = DateTime(1997, 5, 4, 12, 22, 3);
assert(dateTime.toISOString() == "19970504T122203");
assert(dateTime.toISOExtString() == "1997-05-04T12:22:03");
assert(dateTime.toSimpleString() == "1997-May-04 12:22:03");
auto restored = DateTime.fromISOExtString(dateTime.toISOExtString());
assert(dateTime == restored);
--------------------
$(P One area with saving times as strings which gets a bit awkward is time
zones. The time zone is included in the string as part of the ISO
standard, but all it contains is the total offset from UTC at that
particular date and time, so you can't generally use an ISO string
(extended or otherwise) to get the exact time zone which the $(SYSTIME)
originally had. Rather, it will be restored with a $(SIMPLETZ) with the
given offset from UTC (except in the case of UTC, where it can restore
$(UTC)). On the other hand, if you're using $(LOCALTIME), then the time
zone is not part of the string (per the ISO standard), and restoring the
$(SYSTIME) will restore it to whatever the current time zone is on the
box, regardless of what the original time zone was. However, because in
all cases, except for $(LOCALTIME), the UTC offset is included in the
string, it is generally possible to get the exact UTC time that the
$(SYSTIME) was for. But you can't usually restore the original time zone
from just the ISO string.)
--------------------
auto local = SysTime(629_983_705_230_000_035);
auto utc = local.toUTC();
auto other = local.toOtherTZ(TimeZone.getTimeZone("America/New_York"));
//This assumes that you're in "America/Los_Angeles". You'd get a different
//time if you're in a different time zone.
assert(local.toISOExtString() == "1997-05-04T12:22:03.0000035");
assert(utc.toISOExtString() == "1997-05-04T19:22:03.0000035Z");
assert(other.toISOExtString() == "1997-05-04T15:22:03.0000035-04:00");
auto restLocal = SysTime.fromISOExtString(local.toISOExtString());
auto restUTC = SysTime.fromISOExtString(utc.toISOExtString());
auto restOther = SysTime.fromISOExtString(other.toISOExtString());
//Only guaranteed because it's on the same machine.
assert(restLocal == local);
//Guaranteed regardless of machine. Their internal values could differ however.
assert(cast(DateTime)restLocal == cast(DateTime)local);
//Time zone is UTC for both.
assert(restUTC == utc);
//Time zone for restOther is SimpleTimeZone(-4 * 60), not "America/New_York".
assert(restOther == other);
--------------------
$(P To summarize, $(UTC) and $(SIMPLETZ) can be restored exactly using an
ISO or ISO extended string. However, none of the other $(TIMEZONE)s can
be. $(LOCALTIME) is restored with the same date and time but in the
local time zone of the computer it's restored on, so its std time may
differ. Other time zones end up with the restored $(SYSTIME) having the
same std time as the original, but the new time zone is a $(SIMPLETZ)
with the same total UTC offset which the original time zone had at the
given std time, but you don't get the original time zone back. That
works just fine if you don't ever need to change the value of that
$(SYSTIME) or need to know the name of the original time zone, but it is
inadequate if you need to do either of those, since the rules for
the new time zone won't match those of the original.)
$(P So, if you don't care about the time zone or if the restored $(SYSTIME)
has the same std time as the original, then $(LOCALTIME) is fine.
However, if you want the std time to be consistent, then avoid
$(LOCALTIME). In most cases, I'd advise simply using $(UTC) as the
time zone when saving the time. And if you want to restore the time zone
such that it's the same time zone with the same rules as it was prior to
saving the time, then you're going to need to save that information
yourself. With a $(POSIXTZ) or a $(WINDOWSTZ), all you have to do is
save the time zone's $(D name) (which for them is the TZ database name
and the Windows time zone name of that time zone respectively). That can
be used to restore the time zone. If it's a $(SIMPLETZ) or $(UTC), then
you don't have to do anything, because the ISO string will be enough. If
you're using $(LOCALTIME), however, you're in a bit of a bind.)
$(P The restored time zone will be $(LOCALTIME), so if you want it to be
whatever the local time of the computer you're doing the restoring on
is, then you're fine. But if you want to be able to have the same actual
time zone restored regardless of the local time of the computer
restoring the time, you'll need to figure out what the time zone's TZ
database name or Windows time zone name is on the original computer so
that you can use it to get its corresponding $(POSIXTZ) or $(WINDOWSTZ)
on the computer that's doing the restoring. But it's actually
$(I really) hard to accurately determine the TZ database name or Windows
time zone name of the local time zone on any OS other than on Windows,
so $(STD_DATETIME) doesn't currently provide a way to do that. I expect
that such a requirement would be quite rare however. In most cases,
you'll care about $(LOCALTIME) and/or $(UTC), and even if you're using
$(POSIXTZ) or $(WINDOWSTZ), odds are that restoring the time with the
correct std time value and correct UTC offset will be enough (and if
it's not, you can always save the time zone's name to restore the
correct $(POSIXTZ) or $(WINDOWSTZ)). It's just $(LOCALTIME) that has
the problem. However, if a function to accurately determine the TZ
database name of the local time zone on Posix systems is ever devised,
then it will be added to $(STD_DATETIME).)
$(P The other, more compact, option for saving a $(SYSTIME) is to just save
its std time as a 64-bit integer. It's not really humanly readable
like an ISO or ISO extended string would be, but it takes up less space
if saved as an actual number rather than a string. However, you do then
have to worry about the time zone yourself entirely if you wish to be
able to restore it. But if you save the current UTC offset (meaning
the UTC offset with the DST offset applied - such as an ISO string would
include), that would be enough to correctly give what the time would
have been in the original time zone even if you can't restore that time
zone.)
$(P Well, that's probably more than enough on time zones. In most cases, you
shouldn't need to care about them ($(SYSTIME) is designed to make it
so that you shouldn't have to worry about them if you don't want to),
but $(STD_DATETIME) strives to give the best tools possible for handling
time zones when you actually want to. Regardless, by far the biggest
gain that $(SYSTIME) gives you is that its internal time is always in
UTC, so regardless of whether you try and do anything with time zones
explicitly, you won't have any problems with DST changes when dealing
with $(SYSTIME).)
$(P One last suggestion on using $(SYSTIME) would be that if you need to
query it for more than one of its properties (e.g. $(D day) or
$(D hour)), or if you need to do it many times in a row, and the
$(SYSTIME) isn't going to change, then you should probably cast it to
another time point type (probably $(DATETIME)) and query $(I it) for
those values. The reason for this is that every time that you call a
property function on a $(SYSTIME), it has to convert its internal std
time to the value of the property that you're asking for, whereas if you
convert it to a $(DATETIME), the $(DATETIME) holds those values
separately, and you only have to do the calculations once - when you
do the conversion from the $(SYSTIME) to a $(DATETIME). If what you're
doing doesn't need that extra boost of efficiency, then you might as
well not bother, but it is good to be aware that it's less efficient to
query each of $(SYSTIME)'s properties individually rather than
converting it to a $(DATETIME) and then querying it.)
--------------------
auto st = Clock.currTime();
//Each value must be individually calculated.
{
auto year = st.year;
auto month = st.month;
auto day = st.day;
auto hour = st.hour;
auto minute = st.minute;
auto second = st.second;
auto fracSec = st.fracSec;
}
/+
You do the calculations only twice if you convert to a DateTime
(twice instead of once, because you're still asking for FracSec
separately, though that particular calculation is fairly cheap).
+/
auto dateTime = cast(DateTime)st;
{
auto year = dateTime.year;
auto month = dateTime.month;
auto day = dateTime.day;
auto hour = dateTime.hour;
auto minute = dateTime.minute;
auto second = dateTime.second;
auto fracSec = st.fracSec;
}
--------------------
$(H2 Migrating to std.datetime)
$(P Okay, hopefully you have a fair idea of the basics of $(STD_DATETIME)
at this point (though there's plenty more which is covered in the
documentation), but the big question for many is how best to handle
converting your code from using $(STD_DATE) to using $(STD_DATETIME).
When using $(STD_DATE), you would have been using $(D d_time), and if
you had to worry about time zones, you were probably either using C
functions to deal with conversions or just doing them yourself, since
the functionality in $(STD_DATE) which relates to time zones is rather
broken. As a result, most of what you would have done would likely be in
UTC.)
$(P $(SYSTIME) holds its time internally in UTC in a manner similar to
$(D d_time) (albeit with different units and a different epoch), and it
is the type intended for dealing with the time from the system's clock,
so $(SYSTIME) is generally what $(D d_time) should be replaced with.
Functions in Phobos which previously took or returned a $(D d_time) now
take or return a $(SYSTIME) or are scheduled to be deprecated and have
replacement functions which take or return a $(SYSTIME). Generally, in
the case where a function took a $(D d_time), that function is now
overloaded with a version which takes a $(SYSTIME), but in cases where a
function could not be overloaded (such as when it simply returned a
$(D d_time)), a new function has been added to replace the old one (so
as to avoid breaking existing code). The module that this impacts the
most is $(STD_FILE). For example,)
--------------------
d_time dTime = "myfile.txt".lastModified();
SysTime sysTime = "myfile.txt".timeLastModified();
setTimes("yourfile.txt", dTime, dTime + 5);
setTimes("yourfile.txt", sysTime, sysTime + dur!"msecs"(5));
--------------------
$(P With all such functions, it's simply a matter of changing the type of
the argument that you're passing to the function or assigning its
return value to and possibly changing the function name that you're
calling so that it's the version that returns a $(SYSTIME). Those
changes are quite straightforward and not particularly disruptive. Of
greater concern are the formats that times are printed or saved in
and how time zones are dealt with.)
$(P If you were saving the integral $(D d_time) value anywhere, then you're
either going to have to switch to saving a value that $(SYSTIME) would
use as discussed previously (such as its std time or its ISO string),
or you're going to have be converting between $(D d_time) and
$(SYSTIME). At present, the functions
$(REF sysTimeToDTime, std,datetime) and
$(REF dTimeToSysTime, std,datetime) will do those conversions for you.
So, converting between the two formats is easy. However, because
$(D d_time) is going away, those functions will be going away. That
means that you either need to refactor your code so that those functions
aren't necessary, or you need to copy them to your own code to continue
to use them.)
$(P As for formatted strings, $(STD_DATETIME) currently only supports
ISO strings, ISO extended strings, and Boost's simple string.
Eventually, it should have functions for custom strings, but a
well-designed function for creating custom strings based on format
strings is not easy to design, and it hasn't been done for
$(STD_DATETIME) yet (it's on my todo list, but it could be a while
before I get to it). So, in general, you're either going to have to
switch to using one of the string formats that $(STD_DATETIME)
supports, or you're going to have to generate and parse the string
format that you want yourself. In some cases, you should be able to
adjust the string that $(D core.stdc.time.ctime) gives you, and in
others, you may be able to use $(D toISOExtString) and adjust what
$(I it) gives you, but there's a decent chance that you're going to
have to just create and parse the strings yourself using the various
properties on $(SYSTIME). One major difference between the string
functions in $(STD_DATE) and those in $(STD_DATETIME) to note is that
unlike $(STD_DATE), aside from Boost's simple strings, nothing in
$(STD_DATETIME) prints the names of months or weekdays, because that
poses a localization issue. So, unless you're using
$(D core.stdc.time.ctime) to get those values, you're going to have
to create the names yourself.)
$(P Now, if you were doing anything with time zones with $(STD_DATE), odds
are that you were doing all of those conversions yourself (since that's
one of the areas where $(STD_DATE) is buggy). That being the case,
you probably have the offset from UTC and the offset adjustment for
DST for whatever time zone you're dealing with. What is likely the
best way to handle that is to create a $(SIMPLETZ) using those values.
Simply calculate the total UTC offset (so add in the DST offset if
it applies for the date in question) in minutes and create a $(SIMPLETZ)
with that. Note that $(STD_DATETIME) treats west of UTC as
$(I negative) (for some reason, some systems - particularly Posix stuff
- use a positive offset from UTC when west of UTC, in spite of the fact
that when talking about time zones, negative is always used for west of
UTC, and that's what the ISO standard strings do). So, you may have to
adjust your values accordingly. Regardless, be very careful to make
sure that you understand what the values you've been using represent
in units of time and whether you need to be adding or subtracting them
to convert them to what $(SIMPLETZ) expects for its offset from UTC:
the minutes to $(I add) to the time in UTC to get the time in the
target time zone.)
--------------------
//These are the same offsets as America/Los_Angeles.
auto utcOffset = -8 * 60;
auto dstOffset = 60;
immutable tzWithDST = new SimpleTimeZone(utcOffset + dstOffset);
immutable tzWithoutDST = new SimpleTimeZone(utcOffset);
--------------------
$(P The last thing that I have to note is some differences in numerical
values between $(STD_DATE) and $(STD_DATETIME).
$(D std.date.Date)'s $(D weekday) property gives Sunday a value of
1, but $(D std.date.weekDay) gives Sunday a value of 0.
$(REF DayOfWeek, std,datetime) gives Sunday a value of 0. So,
depending on which part of $(STD_DATE) you're dealing with it, it
may or may not match what $(STD_DATETIME) is doing for the numerical
values of weekdays. Months have a similar problem.
$(D std.date.Date)'s $(D month) property gives January a value of
1 - which matches what $(REF Month, std,datetime) does - but
$(D std.date.monthFromTime) gives January a value of 0. So,
just as with the days of the week, you have to be careful with the
numerical values of the months. Whether $(STD_DATETIME) matches what
$(STD_DATE) is doing depends on which part of $(STD_DATE) you're using.
And as you'll notice, it's not even consistent as to whether
$(D std.date.Date) or the free function in $(STD_DATE) is the one
which matches $(STD_DATETIME). So, you should be very careful when
converting code which uses numerical values for either the days of the
week or the months of the year.)
$(BOOKTABLE $(H3 std.date symbols and their std.datetime counterparts),
$(TR $(TD $(B $(STD_DATE)))
$(TD $(B $(STD_DATETIME) Equivalent)))
$(TR $(TD $(D d_time))
$(TD The closest would be $(SYSTIME).))
$(TR $(TD $(D d_time_nan))
$(TD There is no equivalent. $(SYSTIME).$(D init), which has a
$(D null) $(TIMEZONE) object, would be the closest, but once
CTFE advances to the point that you can new up class objects
with it, $(SYSTIME).$(D init)'s $(D timezone) will be
$(LOCALTIME), so don't rely on $(SYSTIME).$(D init) being
invalid. $(STD_DATETIME) in general tries to avoid having any
invalid states for any of its types. It's intended that
creating such values be impossible.))
$(TR $(TD $(D Date))
$(TD $(SYSTIME)))
$(TR $(TD $(D Date).$(D year))
$(TD $(SYSTIME).$(D year)))
$(TR $(TD $(D Date).$(D month))
$(TD $(SYSTIME).$(D month)))
$(TR $(TD $(D Date).$(D day))
$(TD $(SYSTIME).$(D day)))
$(TR $(TD $(D Date).$(D hour))
$(TD $(SYSTIME).$(D hour)))
$(TR $(TD $(D Date).$(D minute))
$(TD $(SYSTIME).$(D minute)))
$(TR $(TD $(D Date).$(D second))
$(TD $(SYSTIME).$(D second)))
$(TR $(TD $(D Date).$(D ms))
$(TD $(SYSTIME).$(D fracSec.msecs)))
$(TR $(TD $(D Date).$(D weekday))
$(TD $(SYSTIME).$(D dayOfWeek) - but note that the values are off
by 1.))
$(TR $(TD $(D Date).$(D tzcorrection))
$(TD
--------------------
immutable tz = sysTime.timezone;
auto diff = tz.utcToTZ(sysTime.stdTime) - sysTime.stdTime;
auto tzcorrection = convert!("hnsecs", "minutes")(diff);
--------------------
However, it looks like $(D tzcorrection) is broken, so you're probably not using
it in your code anyway.
))
$(TR $(TD $(D Date).$(D parse))
$(TD $(SYSTIME).$(D fromISOString),
$(SYSTIME).$(D fromISOExtString), and
$(SYSTIME).$(D fromSimpleString), but the formats of the
strings differ from what $(D std.date.Date).$(D parse)
accepts.))
$(TR $(TD $(D ticksPerSecond))
$(TD There is no equivalent. It's only relevant to $(D d_time).))
$(TR $(TD $(D toISO8601YearWeek))
$(TD $(SYSTIME).$(D isoWeek)))
$(TR $(TD $(D hourFromTime))
$(TD $(SYSTIME).$(D hour)))
$(TR $(TD $(D minFromTime))
$(TD $(SYSTIME).$(D minute)))
$(TR $(TD $(D secFromTime))
$(TD $(SYSTIME).$(D second)))
$(TR $(TD $(D daysInYear))
$(TD $(D sysTime.isLeapYear ? 366 : 365)))
$(TR $(TD $(D dayFromYear))
$(TD $(D (sysTime - SysTime(Date(1970, 1, 1), UTC())).total!"days"())))
$(TR $(TD $(D yearFromTime))
$(TD $(SYSTIME).$(D year)))
$(TR $(TD $(D inLeapYear))
$(TD $(SYSTIME).$(D isLeapYear)))
$(TR $(TD $(D monthFromTime))
$(TD $(SYSTIME).$(D month) - but note that the values are off by
1.))
$(TR $(TD $(D dateFromTime))
$(TD $(SYSTIME).$(D day)))
$(TR $(TD $(D weekDay))
$(TD $(SYSTIME).$(D dayOfWeek)))
$(TR $(TD $(D UTCtoLocalTime))
$(TD $(SYSTIME).$(D toUTC)))
$(TR $(TD $(D dateFromNthWeekdayOfMonth))
$(TD
--------------------
//There is no equivalent. This is a possible implementation.
int dateFromNthWeekdayOfMonth(int year, Month month,
DayOfWeek dow, int n)
{
auto first = Date(year, month, 1);
auto target = first;
immutable targetDOTW = target.dayOfWeek;
if (targetDOTW != dow)
{
if (targetDOTW < dow)
target += dur!"days"(dow - targetDOTW);
else
{
target += dur!"days"((DayOfWeek.sat - targetDOTW) +
dow + 1);
}
}
target += dur!"weeks"(n - 1);
if (target.month != first.month)
target -= dur!"weeks"(1);
return cast(int)((target - first).total!"days"()) + 1;
}
--------------------
))
$(TR $(TD $(D daysInMonth))
$(TD $(SYSTIME).$(D endOfMonthDay); Actually, this name is overly
easy to confuse with $(D endOfMonth) - which returns a
$(SYSTIME) of the last day of the month. I will probably
rename this to $(D daysInMonth). But if I do, it won't be
until the next release (2.054), and this name will be around
until it's gone through the full deprecation cycle.))
$(TR $(TD $(D UTCtoString))
$(TD There is no equivalent. You could probably parse and recombine
$(D core.stdc.time.ctime) and
$(SYSTIME).$(D toISOExtString) to create it though. However,
this function appears to be fairly buggy in the first place,
so odds are that your code isn't using it anyway.))
$(TR $(TD $(D toUTCString))
$(TD There is no equivalent. You could probably parse and recombine
$(D core.stdc.time.ctime) and
$(SYSTIME).$(D toISOExtString) to create it though.))
$(TR $(TD $(D toDateString))
$(TD There is no equivalent. You could probably parse and recombine
$(D core.stdc.time.ctime) and
$(SYSTIME).$(D toISOExtString) to create it though. However,
this function appears to be fairly buggy in the first place,
so odds are that your code isn't using it anyway.))
$(TR $(TD $(D toTimeString))
$(TD There is no equivalent. You could probably parse and recombine
$(D core.stdc.time.ctime) and
$(SYSTIME).$(D toISOExtString) to create it though. However,
this function appears to be fairly buggy in the first place,
so odds are that your code isn't using it anyway.))
$(TR $(TD $(D parse).$(D parse))
$(TD $(SYSTIME).$(D fromISOString),
$(SYSTIME).$(D fromISOExtString), and
$(SYSTIME).$(D fromSimpleString), but the formats of the
strings differ from what $(D std.date.parse) accepts.))
$(TR $(TD $(D getUTCtime))
$(TD $(D Clock.currTime(UTC())) if you want the $(SYSTIME) to have
its time zone be $(UTC). More likely though, you'll just use
$(D Clock.currTime()). Its internal time is in UTC
regardless.))
$(TR $(TD $(D DosFileTime))
$(TD $(REF_SHORT DosFileTime, std,datetime)))
$(TR $(TD $(D toDtime))
$(TD $(REF_SHORT DosFileTimeToSysTime, std,datetime)))
$(TR $(TD $(D toDosFileTime))
$(TD $(REF_SHORT SysTimeToDosFileTime, std,datetime)))
$(TR $(TD $(D benchmark))
$(TD $(REF_SHORT benchmark, std,datetime)))
)
$(P Note that I'm not an expert on what does and doesn't work in
$(STD_DATE), so while I have noted some of the functions that I know to
be broken, just because a function isn't labeled as broken in the above
table does not mean that it works correctly. And any function which
doesn't work correctly is obviously not going to give the same results
as the $(STD_DATETIME) equivalent, since it's almost certain that the
$(STD_DATETIME) version isn't buggy, let alone buggy in the same way (if
it is buggy, the bug is almost certainly going to be far more subtle
than any bug in $(STD_DATE), since $(STD_DATETIME) is quite thoroughly
unit tested).)
$(H2 Conclusion)
$(P Hopefully this article has improved your understanding of
$(STD_DATETIME) and will get you well on your way to being able to
migrate your code from $(STD_DATE) to $(STD_DATETIME). If you have any
further questions, please ask them on the
$(HTTP www.digitalmars.com/NewsGroup.html, digitalmars.D.learn newsgroup).
And if there's a major use case of $(STD_DATE) which is not easy to
convert over to $(STD_DATETIME) which I missed in this
article and you think should be in it, please feel free to bring it up
on the
$(HTTP www.digitalmars.com/NewsGroup.html, digitalmars.D newsgroup),
and if need be, I'll update this article with the relevant information.)
)
Macros:
BOOKTABLE = <center><table cellspacing="0" cellpadding="5" class="book"><caption>$1</caption>$2</table></center>
RED = <span style="color:red">$0</span>
TITLE = Introduction to std.datetime
CORE_TIME=$(MREF core,time)
STD_DATETIME=$(MREF std,datetime)
STD_DATE=$(D std.date)
STD_FILE=$(MREF std,file)
DATE=$(REF_SHORT Date, std,datetime)
DATETIME=$(REF_SHORT DateTime, std,datetime)
DURATION=$(REF_SHORT Duration, core,time)
FRACSEC=$(REF_SHORT FracSec, core,time)
INTERVAL=$(REF_SHORT Interval, std,datetime)
LOCALTIME=$(REF_SHORT LocalTime, std,datetime)
NEGINF_INTERVAL=$(REF_SHORT NegInfInterval, std,datetime)
POSINF_INTERVAL=$(REF_SHORT PosInfInterval, std,datetime)
POSIXTZ=$(REF_SHORT PosixTimeZone, std,datetime)
STOPWATCH=$(REF_SHORT StopWatch, std,datetime)
SIMPLETZ=$(REF_SHORT SimpleTimeZone, std,datetime)
SUBNAV=$(SUBNAV_ARTICLES)
SYSTIME=$(REF_SHORT SysTime, std,datetime)
TIMEOFDAY=$(REF_SHORT TimeOfDay, std,datetime)
TICK_DURATION=$(REF_SHORT TickDuration, core,time)
TIMEZONE=$(REF_SHORT TimeZone, std,datetime)
UTC=$(REF_SHORT UTC, std,datetime)
WINDOWSTZ=$(REF_SHORT WindowsTimeZone, std,datetime)
_=