-
Notifications
You must be signed in to change notification settings - Fork 225
Expand file tree
/
Copy pathstdlib_datetime.f90
More file actions
803 lines (724 loc) · 26.4 KB
/
Copy pathstdlib_datetime.f90
File metadata and controls
803 lines (724 loc) · 26.4 KB
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
794
795
796
797
798
799
800
801
802
803
module stdlib_datetime
!! version: experimental
!!
!! Date, time, and time interval handling for Fortran.
!! ([Specification](../page/specs/stdlib_datetime.html))
use stdlib_kinds, only: dp, int64
use stdlib_strings, only: to_string
implicit none
private
public :: datetime_type, timedelta_type
public :: datetime, timedelta, now, now_utc, epoch
public :: parse_datetime, format_datetime, format_timedelta
public :: is_leap_year, days_in_month, days_in_year
public :: day_of_year, day_of_week, to_utc, total_seconds
public :: operator(+), operator(-)
public :: operator(==), operator(/=)
public :: operator(<), operator(<=)
public :: operator(>), operator(>=)
type :: datetime_type
!! version: experimental
!!
!! Represents a specific point in time.
integer :: year = 1 !! Year (1-9999)
integer :: month = 1 !! Month (1-12)
integer :: day = 1 !! Day (1-31)
integer :: hour = 0 !! Hour (0-23)
integer :: minute = 0 !! Minute (0-59)
integer :: second = 0 !! Second (0-59)
integer :: millisecond = 0 !! Millisecond (0-999)
integer :: utc_offset_minutes = 0 !! UTC offset in minutes
end type datetime_type
type :: timedelta_type
!! version: experimental
!!
!! Represents a duration or time interval.
!! Normalized: seconds in [0,86399], ms in [0,999].
!! Days can be negative for negative durations.
integer :: days = 0 !! Number of days
integer :: seconds = 0 !! Seconds (0-86399)
integer :: milliseconds = 0 !! Milliseconds (0-999)
end type timedelta_type
integer(int64), parameter :: MS_PER_SEC = 1000_int64
integer(int64), parameter :: MS_PER_MIN = 60000_int64
integer(int64), parameter :: MS_PER_HOUR = 3600000_int64
integer(int64), parameter :: MS_PER_DAY = 86400000_int64
interface operator(+)
module procedure dt_plus_td
module procedure td_plus_dt
module procedure td_plus_td
end interface
interface operator(-)
module procedure dt_minus_td
module procedure dt_minus_dt
module procedure td_minus_td
module procedure td_negate
end interface
interface operator(==)
module procedure dt_eq
module procedure td_eq
end interface
interface operator(/=)
module procedure dt_ne
module procedure td_ne
end interface
interface operator(<)
module procedure dt_lt
module procedure td_lt
end interface
interface operator(<=)
module procedure dt_le
module procedure td_le
end interface
interface operator(>)
module procedure dt_gt
module procedure td_gt
end interface
interface operator(>=)
module procedure dt_ge
module procedure td_ge
end interface
interface is_leap_year
module procedure is_leap_year_int
module procedure is_leap_year_dt
end interface
contains
pure function datetime(year, month, day, hour, minute, &
second, millisecond, &
utc_offset_minutes) result(dt)
!! version: experimental
!!
!! Create a datetime_type from individual components.
integer, intent(in), optional :: year, month, day
integer, intent(in), optional :: hour, minute, second
integer, intent(in), optional :: millisecond
integer, intent(in), optional :: utc_offset_minutes
type(datetime_type) :: dt
if (present(year)) dt%year = year
if (present(month)) dt%month = month
if (present(day)) dt%day = day
if (present(hour)) dt%hour = hour
if (present(minute)) dt%minute = minute
if (present(second)) dt%second = second
if (present(millisecond)) dt%millisecond = millisecond
if (present(utc_offset_minutes)) &
dt%utc_offset_minutes = utc_offset_minutes
end function datetime
pure function timedelta(days, hours, minutes, seconds, &
milliseconds) result(td)
!! version: experimental
!!
!! Create a normalized timedelta_type from mixed units.
integer, intent(in), optional :: days, hours, minutes
integer, intent(in), optional :: seconds, milliseconds
type(timedelta_type) :: td
integer(int64) :: total_ms
total_ms = 0_int64
if (present(days)) &
total_ms = total_ms &
+ int(days, int64) * MS_PER_DAY
if (present(hours)) &
total_ms = total_ms &
+ int(hours, int64) * MS_PER_HOUR
if (present(minutes)) &
total_ms = total_ms &
+ int(minutes, int64) * MS_PER_MIN
if (present(seconds)) &
total_ms = total_ms &
+ int(seconds, int64) * MS_PER_SEC
if (present(milliseconds)) &
total_ms = total_ms + int(milliseconds, int64)
td = ms_to_td(total_ms)
end function timedelta
function now() result(dt)
!! version: experimental
!!
!! Return the current local date and time.
type(datetime_type) :: dt
integer :: v(8)
call date_and_time(values=v)
dt = datetime_type(v(1), v(2), v(3), v(5), &
v(6), v(7), v(8), v(4))
end function now
function now_utc() result(dt)
!! version: experimental
!!
!! Return the current UTC date and time.
type(datetime_type) :: dt
dt = to_utc(now())
end function now_utc
pure function epoch() result(dt)
!! version: experimental
!!
!! Return the Unix epoch: 1970-01-01T00:00:00Z.
type(datetime_type) :: dt
dt = datetime_type(1970, 1, 1, 0, 0, 0, 0, 0)
end function epoch
pure function dt_plus_td(dt, td) result(res)
!! datetime + timedelta
type(datetime_type), intent(in) :: dt
type(timedelta_type), intent(in) :: td
type(datetime_type) :: res
res = epoch_ms_to_dt( &
dt_to_epoch_ms(dt) + td_to_ms(td), &
dt%utc_offset_minutes)
end function dt_plus_td
pure function td_plus_dt(td, dt) result(res)
!! timedelta + datetime (commutative)
type(timedelta_type), intent(in) :: td
type(datetime_type), intent(in) :: dt
type(datetime_type) :: res
res = dt_plus_td(dt, td)
end function td_plus_dt
pure function td_plus_td(td1, td2) result(res)
!! timedelta + timedelta
type(timedelta_type), intent(in) :: td1, td2
type(timedelta_type) :: res
res = ms_to_td(td_to_ms(td1) + td_to_ms(td2))
end function td_plus_td
pure function dt_minus_td(dt, td) result(res)
!! datetime - timedelta
type(datetime_type), intent(in) :: dt
type(timedelta_type), intent(in) :: td
type(datetime_type) :: res
res = epoch_ms_to_dt( &
dt_to_epoch_ms(dt) - td_to_ms(td), &
dt%utc_offset_minutes)
end function dt_minus_td
pure function dt_minus_dt(dt1, dt2) result(res)
!! datetime - datetime (both converted to UTC)
type(datetime_type), intent(in) :: dt1, dt2
type(timedelta_type) :: res
res = ms_to_td(dt_to_utc_ms(dt1) - dt_to_utc_ms(dt2))
end function dt_minus_dt
pure function td_minus_td(td1, td2) result(res)
!! timedelta - timedelta
type(timedelta_type), intent(in) :: td1, td2
type(timedelta_type) :: res
res = ms_to_td(td_to_ms(td1) - td_to_ms(td2))
end function td_minus_td
pure function td_negate(td) result(res)
!! Unary minus: -timedelta
type(timedelta_type), intent(in) :: td
type(timedelta_type) :: res
res = ms_to_td(-td_to_ms(td))
end function td_negate
pure function dt_eq(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) == dt_to_utc_ms(dt2)
end function dt_eq
pure function dt_ne(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) /= dt_to_utc_ms(dt2)
end function dt_ne
pure function dt_lt(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) < dt_to_utc_ms(dt2)
end function dt_lt
pure function dt_le(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) <= dt_to_utc_ms(dt2)
end function dt_le
pure function dt_gt(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) > dt_to_utc_ms(dt2)
end function dt_gt
pure function dt_ge(dt1, dt2) result(res)
type(datetime_type), intent(in) :: dt1, dt2
logical :: res
res = dt_to_utc_ms(dt1) >= dt_to_utc_ms(dt2)
end function dt_ge
pure function td_eq(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) == td_to_ms(td2)
end function td_eq
pure function td_ne(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) /= td_to_ms(td2)
end function td_ne
pure function td_lt(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) < td_to_ms(td2)
end function td_lt
pure function td_le(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) <= td_to_ms(td2)
end function td_le
pure function td_gt(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) > td_to_ms(td2)
end function td_gt
pure function td_ge(td1, td2) result(res)
type(timedelta_type), intent(in) :: td1, td2
logical :: res
res = td_to_ms(td1) >= td_to_ms(td2)
end function td_ge
pure function format_datetime(dt) result(str)
!! version: experimental
!!
!! Format a datetime_type as an ISO 8601 string.
type(datetime_type), intent(in) :: dt
character(:), allocatable :: str
integer :: off_h, off_m
str = to_string(dt%year, '(I4.4)') // '-' // &
to_string(dt%month, '(I2.2)') // '-' // &
to_string(dt%day, '(I2.2)') // 'T' // &
to_string(dt%hour, '(I2.2)') // ':' // &
to_string(dt%minute, '(I2.2)') // ':' // &
to_string(dt%second, '(I2.2)')
if (dt%millisecond /= 0) then
str = str // '.' // to_string(dt%millisecond, '(I3.3)')
end if
if (dt%utc_offset_minutes == 0) then
str = str // 'Z'
else
off_h = abs(dt%utc_offset_minutes) / 60
off_m = mod(abs(dt%utc_offset_minutes), 60)
if (dt%utc_offset_minutes > 0) then
str = str // '+'
else
str = str // '-'
end if
str = str // to_string(off_h, '(I2.2)') // ':' // &
to_string(off_m, '(I2.2)')
end if
end function format_datetime
pure function format_timedelta(td) result(str)
!! version: experimental
!!
!! Format a timedelta_type as a readable string.
!! When days == 0, sub-second/sub-minute durations are formatted
!! compactly (e.g. "5ms", "1.500s", "05:30"). The "0 days, "
!! prefix is omitted. When days /= 0, original verbose format
!! is preserved.
type(timedelta_type), intent(in) :: td
character(:), allocatable :: str
integer :: h, m, s, ms
h = td%seconds / 3600
m = mod(td%seconds, 3600) / 60
s = mod(td%seconds, 60)
ms = td%milliseconds
if (td%days == 0) then
! Compact formatting for zero-day durations
if (h == 0 .and. m == 0 .and. s == 0) then
if (ms == 0) then
str = "0s"
else
str = to_string(ms, '(I0)') // "ms"
end if
return
end if
if (h == 0 .and. m == 0) then
if (ms == 0) then
str = to_string(s, '(I0)') // "s"
else
str = to_string(s, '(I0)') // "." // to_string(ms, '(I3.3)') // "s"
end if
return
end if
if (h > 0) then
str = to_string(h, '(I2.2)') // ":" // &
to_string(m, '(I2.2)') // ":" // &
to_string(s, '(I2.2)')
else
str = to_string(m, '(I2.2)') // ":" // &
to_string(s, '(I2.2)')
end if
if (ms /= 0) str = str // "." // to_string(ms, '(I3.3)')
else
! Original format for durations with days
str = to_string(td%days, '(I0)') // ' days, ' // &
to_string(h, '(I2.2)') // ':' // &
to_string(m, '(I2.2)') // ':' // &
to_string(s, '(I2.2)')
if (ms /= 0) then
str = str // '.' // to_string(ms, '(I3.3)')
end if
end if
end function format_timedelta
function parse_datetime(str, stat) result(dt)
!! version: experimental
!!
!! Parse an ISO 8601 date/time string.
character(len=*), intent(in) :: str
integer, intent(out), optional :: stat
type(datetime_type) :: dt
integer :: slen, ios, off_h, off_m, ms_end
integer :: max_day
character(len=1) :: sign_ch
character(len=32) :: tmp_str
real(dp) :: ms_frac
if (present(stat)) stat = 0
dt = datetime_type()
slen = len_trim(str)
! Require at least YYYY-MM-DD (10 characters)
if (slen < 10) then
if (present(stat)) stat = 1
return
end if
! Check required date separators for ISO 8601 (YYYY-MM-DD)
if (str(5:5) /= '-' .or. str(8:8) /= '-') then
if (present(stat)) stat = 1
return
end if
read(str(1:4), '(I4)', iostat=ios) dt%year
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
read(str(6:7), '(I2)', iostat=ios) dt%month
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate month range [1,12]
if (dt%month < 1 .or. dt%month > 12) then
if (present(stat)) stat = 1
return
end if
read(str(9:10), '(I2)', iostat=ios) dt%day
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate day range [1, days_in_month]
max_day = days_in_month(dt%month, dt%year)
if (dt%day < 1 .or. dt%day > max_day) then
if (present(stat)) stat = 1
return
end if
if (slen == 10) return
if (str(11:11) /= 'T' .and. &
str(11:11) /= 't' .and. &
str(11:11) /= ' ') then
if (present(stat)) stat = 1
return
end if
if (slen < 19) then
if (present(stat)) stat = 1
return
end if
! Validate required time separators (HH:MM:SS)
if (str(14:14) /= ':' .or. str(17:17) /= ':') then
if (present(stat)) stat = 1
return
end if
read(str(12:13), '(I2)', iostat=ios) dt%hour
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate hour range [0,23]
if (dt%hour < 0 .or. dt%hour > 23) then
if (present(stat)) stat = 1
return
end if
read(str(15:16), '(I2)', iostat=ios) dt%minute
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate minute range [0,59]
if (dt%minute < 0 .or. dt%minute > 59) then
if (present(stat)) stat = 1
return
end if
read(str(18:19), '(I2)', iostat=ios) dt%second
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate second range [0,59]
if (dt%second < 0 .or. dt%second > 59) then
if (present(stat)) stat = 1
return
end if
if (slen == 19) return
ms_end = 19
if (str(20:20) == '.') then
ms_end = 20
do while (ms_end < slen)
sign_ch = str(ms_end+1:ms_end+1)
if (sign_ch >= '0' .and. sign_ch <= '9') then
ms_end = ms_end + 1
else
exit
end if
end do
if (ms_end == 20) then
! "." without following digits
if (present(stat)) stat = 1
return
end if
tmp_str = '0' // str(20:ms_end)
read(tmp_str, *, iostat=ios) ms_frac
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
dt%millisecond = nint(ms_frac * 1000.0_dp)
end if
if (slen <= ms_end) return
sign_ch = str(ms_end+1:ms_end+1)
if (sign_ch == 'Z' .or. sign_ch == 'z') then
dt%utc_offset_minutes = 0
else if (sign_ch == '+' .or. sign_ch == '-') then
if (slen < ms_end + 6) then
if (present(stat)) stat = 1
return
end if
read(str(ms_end+2:ms_end+3), '(I2)', &
iostat=ios) off_h
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Require ':' between offset hours and minutes
if (str(ms_end+4:ms_end+4) /= ':') then
if (present(stat)) stat = 1
return
end if
read(str(ms_end+5:ms_end+6), '(I2)', &
iostat=ios) off_m
if (ios /= 0) then
if (present(stat)) stat = 1
return
end if
! Validate timezone offset ranges
if (off_h < 0 .or. off_h > 23 .or. &
off_m < 0 .or. off_m > 59) then
if (present(stat)) stat = 1
return
end if
dt%utc_offset_minutes = off_h * 60 + off_m
if (sign_ch == '-') &
dt%utc_offset_minutes = &
-dt%utc_offset_minutes
else
if (present(stat)) stat = 1
return
end if
end function parse_datetime
pure elemental function is_leap_year_int(year) &
result(res)
!! version: experimental
!!
!! Check if a year is a leap year.
integer, intent(in) :: year
logical :: res
res = (mod(year, 4) == 0 &
.and. mod(year, 100) /= 0) &
.or. (mod(year, 400) == 0)
end function is_leap_year_int
pure elemental function is_leap_year_dt(dt) &
result(res)
!! version: experimental
!!
!! Check if a datetime's year is a leap year.
type(datetime_type), intent(in) :: dt
logical :: res
res = is_leap_year_int(dt%year)
end function is_leap_year_dt
pure function days_in_month(month, year) result(d)
!! version: experimental
!!
!! Return the number of days in a given month.
integer, intent(in) :: month, year
integer :: d
integer, parameter :: mdays(12) = &
[31,28,31,30,31,30,31,31,30,31,30,31]
if (month < 1 .or. month > 12) then
d = 0
return
end if
d = mdays(month)
if (month == 2 .and. is_leap_year_int(year)) &
d = 29
end function days_in_month
pure function days_in_year(year) result(d)
!! version: experimental
!!
!! Return 366 for leap years, 365 otherwise.
integer, intent(in) :: year
integer :: d
d = merge(366, 365, is_leap_year_int(year))
end function days_in_year
pure function day_of_year(dt) result(doy)
!! version: experimental
!!
!! Return the ordinal day of the year (1-366).
type(datetime_type), intent(in) :: dt
integer :: doy
integer, parameter :: cum(12) = &
[0,31,59,90,120,151,181,212,243,273,304,334]
! Guard against invalid month values
if (dt%month < 1 .or. dt%month > 12) then
doy = 0
return
end if
doy = cum(dt%month) + dt%day
if (dt%month > 2 .and. is_leap_year_int(dt%year))&
doy = doy + 1
end function day_of_year
pure function day_of_week(dt) result(dow)
!! version: experimental
!!
!! Return ISO weekday (1=Monday ... 7=Sunday).
type(datetime_type), intent(in) :: dt
integer :: dow
integer :: y, w
integer, parameter :: t(12) = &
[0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
! Guard against invalid month values
if (dt%month < 1 .or. dt%month > 12) then
dow = 0
return
end if
y = dt%year
if (dt%month < 3) y = y - 1
w = mod(y + y/4 - y/100 + y/400 &
+ t(dt%month) + dt%day, 7)
dow = mod(w + 6, 7) + 1
end function day_of_week
pure function to_utc(dt) result(utc_dt)
!! version: experimental
!!
!! Convert a datetime to UTC.
type(datetime_type), intent(in) :: dt
type(datetime_type) :: utc_dt
utc_dt = epoch_ms_to_dt(dt_to_utc_ms(dt), 0)
end function to_utc
pure function total_seconds(td) result(secs)
!! version: experimental
!!
!! Return the total duration in seconds as real(dp).
type(timedelta_type), intent(in) :: td
real(dp) :: secs
secs = real(td%days, dp) * 86400.0_dp &
+ real(td%seconds, dp) &
+ real(td%milliseconds, dp) * 0.001_dp
end function total_seconds
pure function days_from_civil(y, m, d) result(days)
!! Convert civil date to days since 1970-01-01.
!! Howard Hinnant's algorithm (public domain).
integer, intent(in) :: y, m, d
integer(int64) :: days
integer :: yr, era, yoe, doy, doe, mp
yr = y
if (m <= 2) yr = yr - 1
if (yr >= 0) then
era = yr / 400
else
era = (yr - 399) / 400
end if
yoe = yr - era * 400
if (m > 2) then
mp = m - 3
else
mp = m + 9
end if
doy = (153 * mp + 2) / 5 + d - 1
doe = yoe * 365 + yoe/4 - yoe/100 + doy
days = int(era, int64) * 146097_int64 &
+ int(doe, int64) - 719468_int64
end function days_from_civil
pure subroutine civil_from_days(z, y, m, d)
!! Convert days since 1970-01-01 to civil date.
!! Howard Hinnant's algorithm (public domain).
integer(int64), intent(in) :: z
integer, intent(out) :: y, m, d
integer(int64) :: zz, era64
integer :: doe, yoe, doy, mp, era
zz = z + 719468_int64
if (zz >= 0) then
era64 = zz / 146097_int64
else
era64 = (zz - 146096_int64) / 146097_int64
end if
era = int(era64)
doe = int(zz - era64 * 146097_int64)
yoe = (doe - doe/1461 + doe/36524 &
- doe/146096) / 365
y = yoe + era * 400
doy = doe - (365*yoe + yoe/4 - yoe/100)
mp = (5*doy + 2) / 153
d = doy - (153*mp + 2)/5 + 1
if (mp < 10) then
m = mp + 3
else
m = mp - 9
end if
if (m <= 2) y = y + 1
end subroutine civil_from_days
pure function dt_to_epoch_ms(dt) result(ms)
!! Datetime to milliseconds since epoch (local).
type(datetime_type), intent(in) :: dt
integer(int64) :: ms
ms = days_from_civil(dt%year, dt%month, dt%day) &
* MS_PER_DAY &
+ int(dt%hour, int64) * MS_PER_HOUR &
+ int(dt%minute, int64) * MS_PER_MIN &
+ int(dt%second, int64) * MS_PER_SEC &
+ int(dt%millisecond, int64)
end function dt_to_epoch_ms
pure function dt_to_utc_ms(dt) result(ms)
!! Datetime to UTC milliseconds since epoch.
type(datetime_type), intent(in) :: dt
integer(int64) :: ms
ms = dt_to_epoch_ms(dt) &
- int(dt%utc_offset_minutes, int64) * MS_PER_MIN
end function dt_to_utc_ms
pure function epoch_ms_to_dt(ms, utc_offset) result(dt)
!! Milliseconds since epoch to datetime.
integer(int64), intent(in) :: ms
integer, intent(in) :: utc_offset
type(datetime_type) :: dt
integer(int64) :: d, rem
d = ms / MS_PER_DAY
rem = ms - d * MS_PER_DAY
if (rem < 0) then
d = d - 1_int64
rem = rem + MS_PER_DAY
end if
call civil_from_days(d, dt%year, dt%month, dt%day)
dt%hour = int(rem / MS_PER_HOUR)
rem = mod(rem, MS_PER_HOUR)
dt%minute = int(rem / MS_PER_MIN)
rem = mod(rem, MS_PER_MIN)
dt%second = int(rem / MS_PER_SEC)
dt%millisecond = int(mod(rem, MS_PER_SEC))
dt%utc_offset_minutes = utc_offset
end function epoch_ms_to_dt
pure function td_to_ms(td) result(ms)
!! Timedelta to total milliseconds.
type(timedelta_type), intent(in) :: td
integer(int64) :: ms
ms = int(td%days, int64) * MS_PER_DAY &
+ int(td%seconds, int64) * MS_PER_SEC &
+ int(td%milliseconds, int64)
end function td_to_ms
pure function ms_to_td(ms) result(td)
!! Total milliseconds to normalized timedelta.
integer(int64), intent(in) :: ms
type(timedelta_type) :: td
integer(int64) :: rem
td%days = int(ms / MS_PER_DAY)
rem = ms - int(td%days, int64) * MS_PER_DAY
if (rem < 0) then
td%days = td%days - 1
rem = rem + MS_PER_DAY
end if
td%seconds = int(rem / MS_PER_SEC)
td%milliseconds = int(mod(rem, MS_PER_SEC))
end function ms_to_td
end module stdlib_datetime