1
2(********************************************************************)
3(*                                                                  *)
4(*  time.s7i      Time and date library                             *)
5(*  Copyright (C) 1991 - 1994, 2005 - 2013 Thomas Mertes            *)
6(*                                                                  *)
7(*  This file is part of the Seed7 Runtime Library.                 *)
8(*                                                                  *)
9(*  The Seed7 Runtime Library is free software; you can             *)
10(*  redistribute it and/or modify it under the terms of the GNU     *)
11(*  Lesser General Public License as published by the Free Software *)
12(*  Foundation; either version 2.1 of the License, or (at your      *)
13(*  option) any later version.                                      *)
14(*                                                                  *)
15(*  The Seed7 Runtime Library is distributed in the hope that it    *)
16(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
17(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
18(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
19(*  details.                                                        *)
20(*                                                                  *)
21(*  You should have received a copy of the GNU Lesser General       *)
22(*  Public License along with this program; if not, write to the    *)
23(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
24(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
25(*                                                                  *)
26(********************************************************************)
27
28
29include "enable_io.s7i";
30
31
32(**
33 *  Describes times and dates.
34 *  For dates the proleptic Gregorian calendar is used (which assumes
35 *  that the Gregorian calendar was even in effect at dates preceding
36 *  its official introduction). This convention is used according to
37 *  ISO 8601 which also defines that positive and negative years exist
38 *  and that the year preceding 1 is 0. Time is measured in hours,
39 *  minutes, seconds and micro seconds. Additionally information about
40 *  the difference to UTC and a flag indicating daylight saving time
41 *  is maintained also.
42 *)
43const type: time is new object struct
44    var integer: year is 0;
45    var integer: month is 1;
46    var integer: day is 1;
47    var integer: hour is 0;
48    var integer: minute is 0;
49    var integer: second is 0;
50    var integer: micro_second is 0;
51    var integer: timeZone is 0;
52    var boolean: daylightSavingTime is FALSE;
53  end struct;
54
55
56const proc: GET_TIME (inout integer: year, inout integer: month, inout integer: day,
57    inout integer: hour, inout integer: minute, inout integer: second,
58    inout integer: micro_second, inout integer: timeZone,
59    inout boolean: daylightSavingTime)                           is action "TIM_NOW";
60
61const proc: AWAIT_TIME (in integer: year, in integer: month, in integer: day,
62    in integer: hour, in integer: minute, in integer: second,
63    in integer: micro_second, in integer: timeZone)              is action "TIM_AWAIT";
64
65const proc: FROM_TIMESTAMP (in integer: timestamp, inout integer: year,
66    inout integer: month, inout integer: day, inout integer: hour,
67    inout integer: minute, inout integer: second, inout integer: micro_second,
68    inout integer: timeZone, inout boolean: daylightSavingTime)  is action "TIM_FROM_TIMESTAMP";
69
70const proc: SET_LOCAL_TZ (in integer: year, in integer: month, in integer: day,
71    in integer: hour, in integer: minute, in integer: second,
72    inout integer: timeZone, inout boolean: daylightSavingTime)  is action "TIM_SET_LOCAL_TZ";
73
74
75(**
76 *  Determine if a year is a leap year in the Gregorian calendar.
77 *  @return TRUE if the year is a leap year, FALSE otherwise.
78 *)
79const func boolean: isLeapYear (in integer: year) is
80  return (year rem 4 = 0 and year rem 100 <> 0) or year rem 400 = 0;
81
82
83(**
84 *  Determine the number of days in a 'year'.
85 *  @return the number of days in the year.
86 *)
87const func integer: daysInYear (in integer: year) is func
88  result
89    var integer: days is 0;
90  begin
91    if isLeapYear(year) then
92      days := 366;
93    else
94      days := 365;
95    end if;
96  end func;
97
98
99(**
100 *  Determine the number of days in the 'month' of the given 'year'.
101 *  @return the number of days in the month.
102 *)
103const func integer: daysInMonth (in integer: year, in integer: month) is func
104  result
105    var integer: leng is 0;
106  local
107    const set of integer: monthsOfLength31 is {1, 3, 5, 7, 8, 10, 12};
108  begin
109    if month in monthsOfLength31 then
110      leng := 31;
111    else
112      if month = 2 then
113        if isLeapYear(year) then
114          leng := 29;
115        else
116          leng := 28;
117        end if;
118      else
119        leng := 30;
120      end if;
121    end if;
122  end func;
123
124
125(**
126 *  Determine the number of days in the month of a given date.
127 *  @return the number of days in the month.
128 *)
129const func integer: daysInMonth (in time: date) is
130  return daysInMonth(date.year, date.month);
131
132
133(**
134 *  Convert a time to a string with ISO 8601 YYYY-MM-DD date format.
135 *  @return the string result of the conversion.
136 *)
137const func string: strDate (in time: aTime) is
138  return aTime.year  lpad0 4 <& "-" <&
139         aTime.month lpad0 2 <& "-" <&
140         aTime.day   lpad0 2;
141
142
143(**
144 *  Convert a time to a string with ISO 8601 hh:mm:ss time format.
145 *  @return the string result of the conversion.
146 *)
147const func string: strTime (in time: aTime) is func
148  result
149    var string: isoTime is "";
150  begin
151    isoTime := aTime.hour   lpad0 2 <& ":" <&
152               aTime.minute lpad0 2 <& ":" <&
153               aTime.second lpad0 2;
154    if aTime.micro_second <> 0 then
155      isoTime &:= "." <& aTime.micro_second lpad0 6;
156    end if;
157  end func;
158
159
160const func string: strTimeZone (in time: aTime) is func
161  result
162    var string: timeZone is "UTC";
163  begin
164    if aTime.timeZone <> 0 then
165      if aTime.timeZone > 0 then
166        timeZone &:= "+";
167      end if;
168      timeZone &:= str(aTime.timeZone div 60);
169      if aTime.timeZone rem 60 <> 0 then
170        timeZone &:= ":" & str(abs(aTime.timeZone) rem 60);
171      end if;
172    end if;
173    if aTime.daylightSavingTime then
174      timeZone &:= " (DST)";
175    end if;
176  end func;
177
178
179const func string: str_yyyy_mm_dd (in time: aTime, in string: delimiter) is
180  return aTime.year  lpad0 4 <& delimiter <&
181         aTime.month lpad0 2 <& delimiter <&
182         aTime.day   lpad0 2;
183
184
185const func string: str_yy_mm_dd (in time: aTime, in string: delimiter) is
186  return aTime.year rem 100 lpad0 2 <& delimiter <&
187         aTime.month        lpad0 2 <& delimiter <&
188         aTime.day          lpad0 2;
189
190
191const func string: str_mm_dd_yyyy (in time: aTime, in string: delimiter) is
192  return aTime.month lpad0 2 <& delimiter <&
193         aTime.day   lpad0 2 <& delimiter <&
194         aTime.year  lpad0 4;
195
196
197const func string: str_mm_dd_yy (in time: aTime, in string: delimiter) is
198  return aTime.month        lpad0 2 <& delimiter <&
199         aTime.day          lpad0 2 <& delimiter <&
200         aTime.year rem 100 lpad0 2;
201
202
203const func string: str_dd_mm_yyyy (in time: aTime, in string: delimiter) is
204  return aTime.day   lpad0 2 <& delimiter <&
205         aTime.month lpad0 2 <& delimiter <&
206         aTime.year  lpad0 4;
207
208
209const func string: str_dd_mm_yy (in time: aTime, in string: delimiter) is
210  return aTime.day   lpad0 2 <& delimiter <&
211         aTime.month lpad0 2 <& delimiter <&
212         aTime.year  rem 100 lpad0 2;
213
214
215const func string: str_d_m_yyyy (in time: aTime, in string: delimiter) is
216  return str(aTime.day)   <& delimiter <&
217         str(aTime.month) <& delimiter <&
218         aTime.year  lpad0 4;
219
220
221const func string: str_d_m_yy (in time: aTime, in string: delimiter) is
222  return str(aTime.day)   <& delimiter <&
223         str(aTime.month) <& delimiter <&
224         aTime.year  rem 100 lpad0 2;
225
226
227const func string: str_hh_mm (in time: aTime, in string: delimiter) is
228  return aTime.hour   lpad0 2 <& delimiter <&
229         aTime.minute lpad0 2;
230
231
232const func string: str_hh_mm_ss (in time: aTime, in string: delimiter) is
233  return aTime.hour   lpad0 2 <& delimiter <&
234         aTime.minute lpad0 2 <& delimiter <&
235         aTime.second lpad0 2;
236
237
238(**
239 *  Convert a time to a string with ISO 8601 date and time format.
240 *  The time is converted to the YYYY-MM-DD hh:mm:ss format.
241 *  Microseconds, time zone and information about the daylight
242 *  saving time are omitted.
243 *  @return the string result of the conversion.
244 *)
245const func string: strDateTime (in time: aTime) is
246  return aTime.year   lpad0 4 <& "-" <&
247         aTime.month  lpad0 2 <& "-" <&
248         aTime.day    lpad0 2 <& " " <&
249         aTime.hour   lpad0 2 <& ":" <&
250         aTime.minute lpad0 2 <& ":" <&
251         aTime.second lpad0 2;
252
253
254(**
255 *  Convert a time to a string with ISO 8601 date and time format.
256 *  The string has the format YYYY-MM-DD hh:mm:ss.uuuuuu followed by a
257 *  time zone in the format UTC+n and (DST), if it is a daylight
258 *  saving time. The microseconds (uuuuuu) are omitted, if they are
259 *  zero. A time zone of UTC+0 is also omitted.
260 *  @return the string result of the conversion.
261 *)
262const func string: str (in time: aTime) is
263  return strDate(aTime) <& " " <&
264         strTime(aTime) <& " " <&
265         strTimeZone(aTime);
266
267
268(**
269 *  Convert a time to a time literal.
270 *  @return the time literal.
271 *)
272const func string: literal (in time: aTime) is
273  return "time(" & literal(str(aTime)) & ")";
274
275
276(**
277 *  Convert a string in the ISO 8601 date format to a time.
278 *  The accepted ISO 8601 date formats are YYYY-MM, YYYY-MM-DD,
279 *  YYYY-MM-DDTHH, YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH:MM:SS
280 *  Additionally a space is also allowed to separate the date and the
281 *  time representations: YYYY-MM-DD HH, YYYY-MM-DD HH:MM,
282 *  YYYY-MM-DD HH:MM:SS
283 *  @return the time result of the conversion.
284 *  @exception RANGE_ERROR If stri contains not a valid time value.
285 *)
286const func time: time (in var string: stri) is func
287  result
288    var time: aTime is time.value;
289  local
290    var boolean: checkForTimeZone is FALSE;
291  begin
292    if stri[1] = '-' then
293      stri := stri[2 ..];
294      aTime.year := -integer(getint(stri));
295    else
296      aTime.year := integer(getint(stri));
297    end if;
298    if stri <> "" and stri[1] = '-' then
299      stri := stri[2 ..];
300      aTime.month := integer(getint(stri));
301      if stri <> "" and stri[1] = '-' then
302        stri := stri[2 ..];
303        aTime.day := integer(getint(stri));
304        if stri <> "" and (stri[1] = ' ' or stri[1] = 'T') then
305          stri := stri[2 ..];
306          aTime.hour := integer(getint(stri));
307          if stri <> "" and stri[1] = ':' then
308            stri := stri[2 ..];
309            aTime.minute := integer(getint(stri));
310            if stri <> "" and stri[1] = ':' then
311              stri := stri[2 ..];
312              aTime.second := integer(getint(stri));
313              if stri <> "" and stri[1] = '.' then
314                stri := stri[2 ..];
315                aTime.micro_second := integer((getint(stri) & "00000X")[.. 6]);
316              end if;
317            end if;
318          end if;
319          checkForTimeZone := TRUE;
320        end if;
321      end if;
322    elsif stri <> "" and stri[1] = ':' then
323      stri := stri[2 ..];
324      aTime.hour := aTime.year;
325      aTime.year := 0;
326      aTime.minute := integer(getint(stri));
327      if stri <> "" and stri[1] = ':' then
328        stri := stri[2 ..];
329        aTime.second := integer(getint(stri));
330        if stri <> "" and stri[1] = '.' then
331          stri := stri[2 ..];
332          aTime.micro_second := integer((getint(stri) & "00000X")[.. 6]);
333        end if;
334      end if;
335      checkForTimeZone := TRUE;
336    end if;
337    if checkForTimeZone then
338      if startsWith(stri, " UTC") then
339        stri := stri[5 ..];
340      end if;
341      if stri <> "" then
342        if stri[1] = '+' then
343          stri := stri[2 ..];
344          aTime.timeZone := integer(getint(stri)) * 60;
345          if stri <> "" and stri[1] = ':' then
346            stri := stri[2 ..];
347            aTime.timeZone +:= integer(getint(stri));
348          end if;
349        elsif stri[1] = '-' then
350          stri := stri[2 ..];
351          aTime.timeZone := -integer(getint(stri)) * 60;
352          if stri <> "" and stri[1] = ':' then
353            stri := stri[2 ..];
354            aTime.timeZone -:= integer(getint(stri));
355          end if;
356        end if;
357      end if;
358    end if;
359    if stri <> "" then
360      raise RANGE_ERROR;
361    end if;
362    aTime.daylightSavingTime := FALSE;
363    if aTime.month < 1 or aTime.month > 12 or
364        aTime.day < 1 or aTime.day > daysInMonth(aTime.year, aTime.month) or
365        aTime.hour < 0 or aTime.hour > 23 or
366        aTime.minute < 0 or aTime.minute > 59 or
367        aTime.second < 0 or aTime.second > 59 then
368      raise RANGE_ERROR;
369    end if;
370  end func;
371
372
373(**
374 *  Convert a string in the ISO 8601 date format to a time.
375 *  The accepted ISO 8601 date formats are YYYY-MM, YYYY-MM-DD,
376 *  YYYY-MM-DDTHH, YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH:MM:SS
377 *  Additionally a space is also allowed to separate the date and the
378 *  time representations: YYYY-MM-DD HH, YYYY-MM-DD HH:MM,
379 *  YYYY-MM-DD HH:MM:SS
380 *  @return the time result of the conversion.
381 *  @exception RANGE_ERROR If stri contains not a valid time value.
382 *)
383const func time: (attr time) parse (in string: stri) is
384    return time(stri);
385
386
387enable_io(time);
388
389
390(**
391 *  Check if two ''time'' values are equal.
392 *  @return TRUE if both times are equal, FALSE otherwise.
393 *)
394const func boolean: (in time: aTime1) = (in time: aTime2) is
395  return
396    aTime1.year = aTime2.year and
397    aTime1.month = aTime2.month and
398    aTime1.day = aTime2.day and
399    aTime1.hour = aTime2.hour and
400    aTime1.minute = aTime2.minute and
401    aTime1.second = aTime2.second and
402    aTime1.micro_second = aTime2.micro_second;
403
404
405(**
406 *  Check if two ''time'' values are not equal.
407 *  @return FALSE if both times are equal, TRUE otherwise.
408 *)
409const func boolean: (in time: aTime1) <> (in time: aTime2) is
410  return
411    aTime1.year <> aTime2.year or
412    aTime1.month <> aTime2.month or
413    aTime1.day <> aTime2.day or
414    aTime1.hour <> aTime2.hour or
415    aTime1.minute <> aTime2.minute or
416    aTime1.second <> aTime2.second or
417    aTime1.micro_second <> aTime2.micro_second;
418
419
420(**
421 *  Check if ''aTime1'' is less than or equal to ''aTime2''.
422 *  @return TRUE if ''aTime1'' is less than or equal to ''aTime2'',
423 *          FALSE otherwise.
424 *)
425const func boolean: (in time: aTime1) <= (in time: aTime2) is func
426  result
427    var boolean: isLessEqual is FALSE;
428  begin
429    if aTime1.year < aTime2.year then
430      isLessEqual := TRUE;
431    elsif aTime1.year = aTime2.year then
432      if aTime1.month < aTime2.month then
433        isLessEqual := TRUE;
434      elsif aTime1.month = aTime2.month then
435        if aTime1.day < aTime2.day then
436          isLessEqual := TRUE;
437        elsif aTime1.day = aTime2.day then
438          if aTime1.hour < aTime2.hour then
439            isLessEqual := TRUE;
440          elsif aTime1.hour = aTime2.hour then
441            if aTime1.minute < aTime2.minute then
442              isLessEqual := TRUE;
443            elsif aTime1.minute = aTime2.minute then
444              if aTime1.second < aTime2.second then
445                isLessEqual := TRUE;
446              elsif aTime1.second = aTime2.second then
447                if aTime1.micro_second <= aTime2.micro_second then
448                  isLessEqual := TRUE;
449                end if;
450              end if;
451            end if;
452          end if;
453        end if;
454      end if;
455    end if;
456  end func;
457
458
459(**
460 *  Check if ''aTime1'' is greater than or equal to ''aTime2''.
461 *  @return TRUE if ''aTime1'' is greater than or equal to ''aTime2'',
462 *          FALSE otherwise.
463 *)
464const func boolean: (in time: aTime1) >= (in time: aTime2) is func
465  result
466    var boolean: isGreaterEqual is FALSE;
467  begin
468    if aTime1.year > aTime2.year then
469      isGreaterEqual := TRUE;
470    elsif aTime1.year = aTime2.year then
471      if aTime1.month > aTime2.month then
472        isGreaterEqual := TRUE;
473      elsif aTime1.month = aTime2.month then
474        if aTime1.day > aTime2.day then
475          isGreaterEqual := TRUE;
476        elsif aTime1.day = aTime2.day then
477          if aTime1.hour > aTime2.hour then
478            isGreaterEqual := TRUE;
479          elsif aTime1.hour = aTime2.hour then
480            if aTime1.minute > aTime2.minute then
481              isGreaterEqual := TRUE;
482            elsif aTime1.minute = aTime2.minute then
483              if aTime1.second > aTime2.second then
484                isGreaterEqual := TRUE;
485              elsif aTime1.second = aTime2.second then
486                if aTime1.micro_second >= aTime2.micro_second then
487                  isGreaterEqual := TRUE;
488                end if;
489              end if;
490            end if;
491          end if;
492        end if;
493      end if;
494    end if;
495  end func;
496
497
498(**
499 *  Check if ''aTime1'' is less than ''aTime2''.
500 *  @return TRUE if ''aTime1'' is less than ''aTime2'',
501 *          FALSE otherwise.
502 *)
503const func boolean: (in time: aTime1) < (in time: aTime2) is
504  return not aTime1 >= aTime2;
505
506
507(**
508 *  Check if ''aTime1'' is greater than ''aTime2''.
509 *  @return TRUE if ''aTime1'' is greater than ''aTime2'',
510 *          FALSE otherwise.
511 *)
512const func boolean: (in time: aTime1) > (in time: aTime2) is
513  return not aTime1 <= aTime2;
514
515
516(**
517 *  Compares two times.
518 *  @return -1, 0 or 1 if the first argument is considered to be
519 *          respectively less than, equal to, or greater than the
520 *          second.
521 *)
522const func integer: compare (in time: aTime1, in time: aTime2) is func
523  result
524    var integer: signumValue is 0;
525  begin
526    if aTime1.year < aTime2.year then
527      signumValue := -1;
528    elsif aTime1.year > aTime2.year then
529      signumValue := 1;
530    elsif aTime1.month < aTime2.month then
531      signumValue := -1;
532    elsif aTime1.month > aTime2.month then
533      signumValue := 1;
534    elsif aTime1.day < aTime2.day then
535      signumValue := -1;
536    elsif aTime1.day > aTime2.day then
537      signumValue := 1;
538    elsif aTime1.hour < aTime2.hour then
539      signumValue := -1;
540    elsif aTime1.hour > aTime2.hour then
541      signumValue := 1;
542    elsif aTime1.minute < aTime2.minute then
543      signumValue := -1;
544    elsif aTime1.minute > aTime2.minute then
545      signumValue := 1;
546    elsif aTime1.second < aTime2.second then
547      signumValue := -1;
548    elsif aTime1.second > aTime2.second then
549      signumValue := 1;
550    elsif aTime1.micro_second < aTime2.micro_second then
551      signumValue := -1;
552    elsif aTime1.micro_second > aTime2.micro_second then
553      signumValue := 1;
554    end if;
555  end func;
556
557
558(**
559 *  Compute the hash value of a time.
560 *  @return the hash value.
561 *)
562const func integer: hashCode (in time: aTime) is
563  return aTime.year << 6 + aTime.month << 5 + aTime.day << 4 +
564         aTime.hour << 3 + aTime.minute << 2 + aTime.second << 1 +
565         aTime.micro_second;
566
567
568(**
569 *  Truncate 'aTime' to the beginning of a second.
570 *  @return the truncated time.
571 *)
572const func time: truncToSecond (in time: aTime) is func
573  result
574    var time: truncatedTime is time.value;
575  begin
576    truncatedTime := aTime;
577    truncatedTime.micro_second := 0;
578  end func;
579
580
581(**
582 *  Truncate 'aTime' to the beginning of a minute.
583 *  @return the truncated time.
584 *)
585const func time: truncToMinute (in time: aTime) is func
586  result
587    var time: truncatedTime is time.value;
588  begin
589    truncatedTime := aTime;
590    truncatedTime.second := 0;
591    truncatedTime.micro_second := 0;
592  end func;
593
594
595(**
596 *  Truncate 'aTime' to the beginning of a hour.
597 *  @return the truncated time.
598 *)
599const func time: truncToHour (in time: aTime) is func
600  result
601    var time: truncatedTime is time.value;
602  begin
603    truncatedTime := aTime;
604    truncatedTime.minute := 0;
605    truncatedTime.second := 0;
606    truncatedTime.micro_second := 0;
607  end func;
608
609
610(**
611 *  Truncate 'aTime' to the beginning of a day.
612 *  @return the truncated time.
613 *)
614const func time: truncToDay (in time: aTime) is func
615  result
616    var time: truncatedTime is time.value;
617  begin
618    truncatedTime := aTime;
619    truncatedTime.hour := 0;
620    truncatedTime.minute := 0;
621    truncatedTime.second := 0;
622    truncatedTime.micro_second := 0;
623  end func;
624
625
626(**
627 *  Truncate 'aTime' to the beginning of the first day in the month.
628 *  @return the truncated time.
629 *)
630const func time: truncToMonth (in time: aTime) is func
631  result
632    var time: truncatedTime is time.value;
633  begin
634    truncatedTime := aTime;
635    truncatedTime.day := 1;
636    truncatedTime.hour := 0;
637    truncatedTime.minute := 0;
638    truncatedTime.second := 0;
639    truncatedTime.micro_second := 0;
640  end func;
641
642
643(**
644 *  Truncate 'aTime' to the beginning of the first day in the year.
645 *  @return the truncated time.
646 *)
647const func time: truncToYear (in time: aTime) is func
648  result
649    var time: truncatedTime is time.value;
650  begin
651    truncatedTime := aTime;
652    truncatedTime.month := 1;
653    truncatedTime.day := 1;
654    truncatedTime.hour := 0;
655    truncatedTime.minute := 0;
656    truncatedTime.second := 0;
657    truncatedTime.micro_second := 0;
658  end func;
659
660
661(**
662 *  Return the weekday number of 'aDate'.
663 *  @return 1 for monday, 2 for tuesday, and so on up to 7 for sunday.
664 *)
665const func integer: dayOfWeek (in time: aDate) is func
666  result
667    var integer: weekday is 0;
668  local
669    var integer: year is 0;
670    var integer: month is 0;
671  begin
672    year := aDate.year;
673    month := aDate.month;
674    if month <= 2 then
675      decr(year);
676      month +:= 12;
677    end if;
678    weekday := succ(pred(year + year mdiv 4 - year mdiv 100 + year mdiv 400 +
679        (31 * (month - 2)) mdiv 12 + aDate.day) mod 7);
680  end func;
681
682
683(**
684 *  Return the day number in the year of 'aDate'.
685 *  @return 1 for the 1. of january and successive values up to 366.
686 *)
687const func integer: dayOfYear (in time: aDate) is func
688  result
689    var integer: dayOfYear is 0;
690  begin
691    if isLeapYear(aDate.year) then
692      dayOfYear := [](0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)[aDate.month] + aDate.day;
693    else
694      dayOfYear := [](0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[aDate.month] + aDate.day;
695    end if;
696  end func;
697
698
699(**
700 *  Return the week number of the 'year' for the 'dayOfYear'.
701 *  According to ISO 8601: Week number 1 of every year contains the
702 *  4. of january. Since 1st to 3rd of january might be in the
703 *  previous week there can be also a week number 0.
704 *  @return a week number from 0 to 53 for weeks belonging to the year
705 *          of the given date.
706 *)
707const func integer: weekOfYear (in var integer: year, in integer: dayOfYear) is func
708  result
709    var integer: weekNumber is 0;
710  local
711    var integer: weekDayOfJan4 is 0;
712  begin
713    year := pred(year);
714    weekDayOfJan4 := succ(pred(year + year mdiv 4 - year mdiv 100 + year mdiv 400 + 32) mod 7);
715    weekNumber := (dayOfYear + weekDayOfJan4 - 5) mdiv 7 + 1;
716  end func;
717
718
719(**
720 *  Return the week number of the year for 'aDate'.
721 *  According to ISO 8601: Week number 1 of every year contains the
722 *  4. of january. Since 1st to 3rd of january might be in the
723 *  previous week there can be also a week number 0.
724 *  @return a week number from 0 to 53 for weeks belonging to the year
725 *          of the given date.
726 *)
727const func integer: weekOfYear (in time: aDate) is
728  return weekOfYear(aDate.year, dayOfYear(aDate));
729
730
731(**
732 *  Return the year of the ISO 8601 week date for 'aDate'.
733 *  At the beginning and the end of an Gregorian calendar year there
734 *  might be days which belong to a week of the previous or next year.
735 *  For example 2005-01-01 is 2004-W53-6 and 2007-12-31 is 2008-W01-1.
736 *  @return the year in the range 'pred(aDate.year)' to
737 *          'succ(aDate.year)'.
738 *)
739const func integer: weekDateYear (in time: aDate) is func
740  result
741    var integer: weekDateYear is 0;
742  local
743    var integer: weekNumber is 0;
744  begin
745    weekNumber := weekOfYear(aDate.year, dayOfYear(aDate));
746    if weekNumber <= 0 then
747      weekDateYear := pred(aDate.year);
748    elsif weekNumber >= 53 and aDate.day >= 29 and
749        weekOfYear(succ(aDate.year), 31 - aDate.day) = 1 then
750      weekDateYear := succ(aDate.year);
751    else
752      weekDateYear := aDate.year;
753    end if;
754  end func;
755
756
757(**
758 *  Return the week number of the ISO 8601 week date for 'aDate'.
759 *  At the beginning and the end of an Gregorian calendar year there
760 *  might be days which belong to a week of the previous or next year.
761 *  For example 2005-01-01 is 2004-W53-6 and 2007-12-31 is 2008-W01-1.
762 *  @return the week number in the range 1 to 53.
763 *)
764const func integer: weekDateWeek (in time: aDate) is func
765  result
766    var integer: weekNumber is 0;
767  begin
768    weekNumber := weekOfYear(aDate.year, dayOfYear(aDate));
769    if weekNumber <= 0 then
770      weekNumber := weekOfYear(pred(aDate.year), 366);
771    elsif weekNumber >= 53 and aDate.day >= 29 and
772        weekOfYear(succ(aDate.year), 31 - aDate.day) = 1 then
773      weekNumber := 1;
774    end if;
775  end func;
776
777
778const proc: NORMALIZE (inout time: tim) is func
779  local
780    var integer: month_length is 0;
781  begin
782    tim.second := tim.second + tim.micro_second mdiv 1000000;
783    tim.micro_second := tim.micro_second mod 1000000;
784    tim.minute := tim.minute + tim.second mdiv 60;
785    tim.second := tim.second mod 60;
786    tim.hour := tim.hour + tim.minute mdiv 60;
787    tim.minute := tim.minute mod 60;
788    tim.day := tim.day + tim.hour mdiv 24;
789    tim.hour := tim.hour mod 24;
790    tim.year := tim.year + pred(tim.month) mdiv 12;
791    tim.month := succ(pred(tim.month) mod 12);
792    month_length := daysInMonth(tim.year, tim.month);
793    while tim.day > month_length do
794      tim.day := tim.day - month_length;
795      if tim.month < 12 then
796        incr(tim.month);
797      else
798        tim.month := 1;
799        incr(tim.year);
800      end if;
801      month_length := daysInMonth(tim.year, tim.month);
802    end while;
803    while tim.day < 1 do
804      if tim.month > 1 then
805        decr(tim.month);
806      else
807        tim.month := 12;
808        decr(tim.year);
809      end if;
810      tim.day := tim.day + daysInMonth(tim.year, tim.month);
811    end while;
812  end func;
813
814
815(**
816 *  Convert a time to Coordinated Universal Time (UTC).
817 *  @return the time in UTC.
818 *)
819const func time: toUTC (in time: aTime) is func
820  result
821    var time: timeInUTC is time.value;
822  begin
823    timeInUTC := aTime;
824    timeInUTC.minute -:= aTime.timeZone;
825    timeInUTC.timeZone := 0;
826    NORMALIZE(timeInUTC);
827  end func;
828
829
830(**
831 *  Sets timeZone and daylightSavingTime for a given time.
832 *  @return the time in the local time zone.
833 *)
834const func time: setLocalTZ (in time: aTime) is func
835  result
836    var time: localTime is time.value;
837  begin
838    localTime := aTime;
839    SET_LOCAL_TZ(localTime.year, localTime.month, localTime.day,
840        localTime.hour, localTime.minute, localTime.second,
841        localTime.timeZone, localTime.daylightSavingTime);
842  end func;
843
844
845(**
846 *  Compute the julian day number of 'aDate'.
847 *  The julian day number is the number of days that have elapsed
848 *  since January 1, 4713 BC in the proleptic Julian calendar.
849 *  @return the julian day number.
850 *)
851const func integer: julianDayNumber (in time: aDate) is func
852  result
853    var integer: julianDayNumber is 0;
854  local
855    var integer: year is 0;
856    var integer: month is 0;
857  begin
858    year := aDate.year;
859    month := aDate.month;
860    if month <= 2 then
861      decr(year);
862      month +:= 12;
863    end if;
864    julianDayNumber := (1461 * (year + 4800)) mdiv 4 +
865                       (367 * (month - 2)) mdiv 12 -
866                       (3 * ((year + 4900) mdiv 100)) mdiv 4 +
867                       aDate.day - 32075;
868  end func;
869
870
871(**
872 *  Return the time of a 'julianDayNumber'.
873 *  The julian day number is the number of days that have elapsed
874 *  since January 1, 4713 BC in the proleptic Julian calendar.
875 *  @return the time 0:00:00 at the day with the 'julianDayNumber'.
876 *)
877const func time: julianDayNumToTime (in integer: julianDayNumber) is func
878  result
879    var time: aTime is time.value;
880  local
881    var integer: l is 0;
882    var integer: n is 0;
883    var integer: i is 0;
884    var integer: j is 0;
885  begin
886    l := julianDayNumber + 68569;
887    n := (4 * l) mdiv 146097;
888    l := l - (146097 * n + 3) mdiv 4;
889    i := (4000 * (l + 1)) mdiv 1461001;
890    l := l - (1461 * i) mdiv 4 + 31;
891    j := (80 * l) mdiv 2447;
892    aTime.day := l - (2447 * j) mdiv 80;
893    l := j mdiv 11;
894    aTime.month := j + 2 - (12 * l);
895    aTime.year := 100 * (n - 49) + i + l;
896  end func;
897
898
899(**
900 *  Return a time expressed in seconds since the Unix Epoch.
901 *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
902 *  @return the seconds since the Unix Epoch.
903 *)
904const func integer: timestamp1970 (in time: aTime) is func
905  result
906    var integer: seconds is 0;
907  local
908    const integer: DAYS_FROM_0_TO_1970 is 719162; # Days between 0-01-01 and 1970-01-01
909    var integer: yearBefore is 0;
910  begin
911    yearBefore := pred(aTime.year);
912    seconds := (((yearBefore * 365 +
913                  yearBefore mdiv 4 -
914                  yearBefore mdiv 100 +
915                  yearBefore mdiv 400 -
916               DAYS_FROM_0_TO_1970 +
917               pred(dayOfYear(aTime))) * 24 +
918               aTime.hour) * 60 +
919               aTime.minute) * 60 +
920               aTime.second -
921               aTime.timeZone * 60;
922  end func;
923
924
925(**
926 *  Convert a timestamp into a time from the local time zone.
927 *  The timestamp is expressed in seconds since the Unix Epoch.
928 *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
929 *  @return the local time that corresponds to the timestamp.
930 *)
931const func time: timestamp1970ToTime (in integer: timestamp) is func
932  result
933    var time: aTime is time.value;
934  begin
935    FROM_TIMESTAMP(timestamp, aTime.year, aTime.month, aTime.day,
936        aTime.hour, aTime.minute, aTime.second,
937        aTime.micro_second, aTime.timeZone,
938        aTime.daylightSavingTime);
939  end func;
940
941
942const func integer: timestamp1601 (in time: aTime) is func
943  result
944    var integer: nanosecs100 is 0;
945  local
946    const integer: SECONDS_1601_1970 is 11644473600;
947    const integer: NANOSECS100_TICK is 10000000;
948  begin
949    nanosecs100 := (timestamp1970(aTime) + SECONDS_1601_1970) * NANOSECS100_TICK;
950  end func;
951
952
953const func time: timestamp1601ToTime (in integer: timestamp) is func
954  result
955    var time: aTime is time.value;
956  local
957    const integer: SECONDS_1601_1970 is 11644473600;
958    const integer: NANOSECS100_TICK is 10000000;
959    var integer: utcSeconds is 0;
960  begin
961    utcSeconds := timestamp div NANOSECS100_TICK - SECONDS_1601_1970;
962    aTime := timestamp1970ToTime(utcSeconds);
963  end func;
964
965
966(*
967(*####
968 *  Convert a timestamp into Coordinated Universal Time (UTC).
969 *)
970const func time: timestamp1970ToUTC (in integer: timestamp) is func
971  result
972    var time: aTime is time.value;
973  local
974    const integer: SECS_DAY is 24 * 60 * 60;
975    var integer: dayclock is 0;
976    var integer: dayno is 0;
977    var integer: year is 1970;
978  begin
979    dayclock := timestamp mod SECS_DAY;
980    dayno    := timestamp mdiv SECS_DAY;
981    aTime.second := dayclock rem 60;
982    aTime.minute := (dayclock rem 3600) div 60;
983    aTime.hour   := dayclock div 3600;
984    if dayno >= 0 then
985      while dayno >= daysInYear(year) do
986        dayno -:= daysInYear(year);
987        incr(year);
988      end while;
989      aTime.month := 1;
990      while dayno >= daysInMonth(year, aTime.month) do
991        dayno -:= daysInMonth(year, aTime.month);
992        incr(aTime.month);
993      end while;
994      aTime.day := succ(dayno);
995    else
996      decr(year);
997      dayno := pred(-dayno);
998      while dayno >= daysInYear(year) do
999        dayno -:= daysInYear(year);
1000        decr(year);
1001      end while;
1002      aTime.month := 12;
1003      while dayno >= daysInMonth(year, aTime.month) do
1004        dayno -:= daysInMonth(year, aTime.month);
1005        decr(aTime.month);
1006      end while;
1007      aTime.day := daysInMonth(year, aTime.month) - dayno;
1008    end if;
1009    aTime.year := year;
1010  end func;
1011*)
1012
1013
1014(**
1015 *  Compute pseudo-random time in the range [low, high].
1016 *  The random values are uniform distributed.
1017 *  @return a random time such that low <= rand(low, high) and
1018 *          rand(low, high) <= high holds.
1019 *  @exception RANGE_ERROR The range is empty (low > high holds).
1020 *)
1021const func time: rand (in time: low, in time: high) is func
1022  result
1023    var time: randomTime is time.value;
1024  local
1025    var integer: timestampLow is 0;
1026    var integer: timestampHigh is 0;
1027  begin
1028    timestampLow := timestamp1970(low);
1029    timestampHigh := timestamp1970(high);
1030    randomTime := low;
1031    randomTime.second +:= rand(timestampLow, timestampHigh) - timestampLow;
1032    if randomTime.second = low.second then
1033      if randomTime.second = high.second then
1034        randomTime.micro_second := rand(low.micro_second, high.micro_second);
1035      else
1036        randomTime.micro_second := rand(low.micro_second, 999999);
1037      end if;
1038    elsif randomTime.second = high.second then
1039      randomTime.micro_second := rand(0, high.micro_second);
1040    else
1041      randomTime.micro_second := rand(0, 999999);
1042    end if;
1043    NORMALIZE(randomTime);
1044  end func;
1045
1046
1047(**
1048 *  Determine the current local time.
1049 *  @return the current local time.
1050 *)
1051const func time: time (NOW) is func
1052  result
1053    var time: localTime is time.value;
1054  begin
1055    GET_TIME(localTime.year, localTime.month, localTime.day,
1056        localTime.hour, localTime.minute, localTime.second,
1057        localTime.micro_second, localTime.timeZone,
1058        localTime.daylightSavingTime);
1059  end func;
1060
1061
1062(**
1063 *  Return the specified UTC time.
1064 *)
1065const func time: time (in integer: year, in integer: month, in integer: day,
1066    in integer: hour, in integer: minute, in integer: second) is func
1067  result
1068    var time: utcTime is time.value;
1069  begin
1070    utcTime.year   := year;
1071    utcTime.month  := month;
1072    utcTime.day    := day;
1073    utcTime.hour   := hour;
1074    utcTime.minute := minute;
1075    utcTime.second := second;
1076  end func;
1077
1078
1079(**
1080 *  Return the specified time in the specified timeZone.
1081 *)
1082const func time: timeInTimeZone (in integer: year, in integer: month, in integer: day,
1083    in integer: hour, in integer: minute, in integer: second, in integer: timeZone) is func
1084  result
1085    var time: aTime is time.value;
1086  begin
1087    aTime.year     := year;
1088    aTime.month    := month;
1089    aTime.day      := day;
1090    aTime.hour     := hour;
1091    aTime.minute   := minute;
1092    aTime.second   := second;
1093    aTime.timeZone := timeZone;
1094  end func;
1095
1096
1097(**
1098 *  Return the specified UTC date.
1099 *)
1100const func time: date (in integer: year, in integer: month, in integer: day) is func
1101  result
1102    var time: aDate is time.value;
1103  begin
1104    aDate.year  := year;
1105    aDate.month := month;
1106    aDate.day   := day;
1107  end func;
1108
1109
1110(**
1111 *  Wait until 'aTime' is reached
1112 *)
1113const proc: await (ref time: aTime) is func
1114  begin
1115    AWAIT_TIME(aTime.year, aTime.month, aTime.day,
1116        aTime.hour, aTime.minute, aTime.second,
1117        aTime.micro_second, aTime.timeZone);
1118  end func;
1119