Dates.Mod 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. MODULE Dates; (** AUTHOR "be, tf, staubesv"; PURPOSE "Date and time functions"; *)
  2. (** Oberon date & time format:
  3. time: bits 16-12: hours
  4. 11-6: minutes
  5. 5-0: seconds
  6. date: 30-9: count of years from 1900
  7. 8-5: month of year
  8. 4-0: day of month
  9. *)
  10. (* Problem of leap seconds is handled as in POSIX:
  11. http://www.eecis.udel.edu/~mills/leap.html
  12. http://en.wikipedia.org/wiki/Leap_second
  13. *)
  14. IMPORT
  15. Clock;
  16. TYPE
  17. DateTime* = RECORD
  18. year*, month*, day*,
  19. hour*, minute*, second*: LONGINT
  20. END;
  21. VAR
  22. Months-: ARRAY 12 OF ARRAY 10 OF CHAR; (** month's names (January = 0....December=11) *)
  23. Days-: ARRAY 7 OF ARRAY 10 OF CHAR; (** day's names (Moday = 0, .... Sunday = 6) *)
  24. NoDays: ARRAY 12 OF INTEGER;
  25. ZeroDateUnix-, ZeroDateRFC868-, ZeroDateNTP-: DateTime;
  26. (** Date and Time functions *)
  27. (** returns TRUE if 'year' is a leap year *)
  28. PROCEDURE LeapYear*(year: LONGINT): BOOLEAN;
  29. BEGIN
  30. RETURN (year > 0) & (year MOD 4 = 0) & (~(year MOD 100 = 0) OR (year MOD 400 = 0))
  31. END LeapYear;
  32. (** returns the number of days in that month *)
  33. PROCEDURE NofDays*(year, month: LONGINT): LONGINT;
  34. BEGIN
  35. DEC(month);
  36. ASSERT((month >= 0) & (month < 12));
  37. IF (month = 1) & LeapYear(year) THEN RETURN NoDays[1]+1
  38. ELSE RETURN NoDays[month]
  39. END
  40. END NofDays;
  41. (** checks if the values of a DateTime structure are valid *)
  42. PROCEDURE ValidDateTime*(dt: DateTime): BOOLEAN;
  43. BEGIN
  44. RETURN (dt.year > 0) & (dt.month > 0) & (dt.month <= 12) & (dt.day > 0) & (dt.day <= NofDays(dt.year, dt.month)) &
  45. (dt.hour >= 0) & (dt.hour < 24) & (dt.minute >= 0) & (dt.minute < 60) & (dt.second >= 0) & (dt.second < 60)
  46. END ValidDateTime;
  47. (** convert an Oberon date/time to a DateTime structure *)
  48. PROCEDURE OberonToDateTime*(Date, Time: LONGINT): DateTime;
  49. VAR dt: DateTime;
  50. BEGIN
  51. dt.second := Time MOD 64; Time := Time DIV 64;
  52. dt.minute := Time MOD 64; Time := Time DIV 64;
  53. dt.hour := Time MOD 24;
  54. dt.day := Date MOD 32; Date := Date DIV 32;
  55. dt.month := Date MOD 16; Date := Date DIV 16;
  56. dt.year := 1900 + Date;
  57. RETURN dt
  58. END OberonToDateTime;
  59. (** convert a DateTime structure to an Oberon date/time *)
  60. PROCEDURE DateTimeToOberon*(dt: DateTime; VAR date, time: LONGINT);
  61. BEGIN
  62. ASSERT(ValidDateTime(dt));
  63. date := (dt.year-1900)*512 + dt.month*32 + dt.day;
  64. time := dt.hour*4096 + dt.minute*64 + dt.second
  65. END DateTimeToOberon;
  66. (** returns the current date and time *)
  67. PROCEDURE Now*(): DateTime;
  68. VAR d, t: LONGINT;
  69. BEGIN
  70. Clock.Get(t, d);
  71. RETURN OberonToDateTime(d, t)
  72. END Now;
  73. (** returns the ISO 8601 year number, week number & week day (Monday=1, ....Sunday=7) *)
  74. (* algorithm by Rick McCarty, http://personal.ecu.edu/mccartyr/ISOwdALG.txt *)
  75. PROCEDURE WeekDate*(Date: DateTime; VAR year, week, weekday: LONGINT);
  76. VAR doy, i, yy, c, g, jan1: LONGINT; leap: BOOLEAN;
  77. BEGIN
  78. IF ValidDateTime(Date) THEN
  79. leap := LeapYear(Date.year);
  80. doy := Date.day; i := 0;
  81. WHILE (i < Date.month-1) DO doy := doy + NoDays[i]; INC(i) END;
  82. IF leap & (Date.month > 2) THEN INC(doy) END;
  83. yy := (Date.year-1) MOD 100; c := (Date.year-1) - yy; g := yy + yy DIV 4;
  84. jan1 := 1 + (((((c DIV 100) MOD 4) * 5) + g) MOD 7);
  85. weekday := 1 + (((doy + (jan1-1))-1) MOD 7);
  86. IF (doy <= (8-jan1)) & (jan1 > 4) THEN (* falls in year-1 ? *)
  87. year := Date.year-1;
  88. IF (jan1 = 5) OR ((jan1 = 6) & LeapYear(year)) THEN week := 53
  89. ELSE week := 52
  90. END
  91. ELSE
  92. IF leap THEN i := 366 ELSE i := 365 END;
  93. IF ((i - doy) < (4 - weekday)) THEN
  94. year := Date.year + 1;
  95. week := 1
  96. ELSE
  97. year := Date.year;
  98. i := doy + (7-weekday) + (jan1-1);
  99. week := i DIV 7;
  100. IF (jan1 > 4) THEN DEC(week) END
  101. END
  102. END
  103. ELSE
  104. year := -1; week := -1; weekday := -1
  105. END
  106. END WeekDate;
  107. PROCEDURE Equal*(t1, t2 : DateTime) : BOOLEAN;
  108. BEGIN
  109. RETURN
  110. (t1.second = t2.second) & (t1.minute = t2.minute) & (t1.hour = t2.hour) &
  111. (t1.day = t2.day) & (t1.month = t2.month) & (t1.year = t2.year);
  112. END Equal;
  113. (** Returns -1 if (t1 < t2), 0 if (t1 = t2) or 1 if (t1 > t2) *)
  114. PROCEDURE CompareDateTime*(t1, t2 : DateTime) : LONGINT;
  115. VAR result : LONGINT;
  116. PROCEDURE Compare(t1, t2 : LONGINT) : LONGINT;
  117. VAR result : LONGINT;
  118. BEGIN
  119. IF (t1 < t2) THEN result := -1;
  120. ELSIF (t1 > t2) THEN result := 1;
  121. ELSE result := 0;
  122. END;
  123. RETURN result;
  124. END Compare;
  125. BEGIN
  126. ASSERT(ValidDateTime(t1) & (ValidDateTime(t2)));
  127. result := Compare(t1.year, t2.year);
  128. IF (result = 0) THEN
  129. result := Compare(t1.month, t2.month);
  130. IF (result = 0) THEN
  131. result := Compare(t1.day, t2.day);
  132. IF (result = 0) THEN
  133. result := Compare(t1.hour, t2.hour);
  134. IF (result = 0) THEN
  135. result := Compare(t1.minute, t2.minute);
  136. IF (result = 0) THEN
  137. result := Compare(t1.second, t2.second);
  138. END;
  139. END;
  140. END;
  141. END;
  142. END;
  143. RETURN result;
  144. END CompareDateTime;
  145. (** Absolute time difference between t1 and t2.
  146. Note that leap seconds are not counted, see http://www.eecis.udel.edu/~mills/leap.html *)
  147. PROCEDURE TimeDifference*(t1, t2 : DateTime; VAR days, hours, minutes, seconds : LONGINT);
  148. CONST SecondsPerMinute = 60; SecondsPerHour = 3600; SecondsPerDay = 86400;
  149. VAR start, end : DateTime; year, month, second : LONGINT;
  150. BEGIN
  151. IF (CompareDateTime(t1, t2) = -1) THEN start := t1; end := t2; ELSE start := t2; end := t1; END;
  152. IF (start.year = end.year) & (start.month = end.month) & (start.day = end.day) THEN
  153. second := end.second - start.second + (end.minute - start.minute) * SecondsPerMinute + (end.hour - start.hour) * SecondsPerHour;
  154. days := 0;
  155. ELSE
  156. (* use start date/time as reference point *)
  157. (* seconds until end of the start.day *)
  158. second := SecondsPerDay - start.second - start.minute * SecondsPerMinute - start.hour * SecondsPerHour;
  159. IF (start.year = end.year) & (start.month = end.month) THEN
  160. (* days between start.day and end.day *)
  161. days := (end.day - start.day) - 1;
  162. ELSE
  163. (* days until start.month ends excluding start.day *)
  164. days := NofDays(start.year, start.month) - start.day;
  165. IF (start.year = end.year) THEN
  166. (* months between start.month and end.month *)
  167. FOR month := start.month + 1 TO end.month - 1 DO
  168. days := days + NofDays(start.year, month);
  169. END;
  170. ELSE
  171. (* days until start.year ends (excluding start.month) *)
  172. FOR month := start.month + 1 TO 12 DO
  173. days := days + NofDays(start.year, month);
  174. END;
  175. FOR year := start.year + 1 TO end.year - 1 DO (* days between start.years and end.year *)
  176. IF LeapYear(year) THEN days := days + 366; ELSE days := days + 365; END;
  177. END;
  178. FOR month := 1 TO end.month - 1 DO (* days until we reach end.month in end.year *)
  179. days := days + NofDays(end.year, month);
  180. END;
  181. END;
  182. (* days in end.month until reaching end.day excluding end.day *)
  183. days := days + end.day - 1;
  184. END;
  185. (* seconds in end.day *)
  186. second := second + end.second + end.minute * SecondsPerMinute + end.hour * SecondsPerHour;
  187. END;
  188. days := days + (second DIV SecondsPerDay); second := second MOD SecondsPerDay;
  189. hours := second DIV SecondsPerHour; second := second MOD SecondsPerHour;
  190. minutes := second DIV SecondsPerMinute; second := second MOD SecondsPerMinute;
  191. seconds := second;
  192. END TimeDifference;
  193. (** Add/Subtract a number of years to/from dt *)
  194. PROCEDURE AddYears*(VAR dt : DateTime; years : LONGINT);
  195. BEGIN
  196. ASSERT(ValidDateTime(dt));
  197. dt.year := dt.year + years;
  198. ASSERT(ValidDateTime(dt));
  199. END AddYears;
  200. (** Add/Subtract a number of months to/from dt. This will adjust dt.year if necessary *)
  201. PROCEDURE AddMonths*(VAR dt : DateTime; months : LONGINT);
  202. VAR years : LONGINT;
  203. BEGIN
  204. ASSERT(ValidDateTime(dt));
  205. years := months DIV 12;
  206. dt.month := dt.month + (months MOD 12);
  207. IF (dt.month > 12) THEN
  208. dt.month := dt.month - 12;
  209. INC(years);
  210. ELSIF (dt.month < 1) THEN
  211. dt.month := dt.month + 12;
  212. DEC(years);
  213. END;
  214. IF (years # 0) THEN AddYears(dt, years); END;
  215. ASSERT(ValidDateTime(dt));
  216. END AddMonths;
  217. (** Add/Subtract a number of days to/from dt. This will adjust dt.month and dt.year if necessary *)
  218. PROCEDURE AddDays*(VAR dt : DateTime; days : LONGINT);
  219. VAR nofDaysLeft : LONGINT;
  220. BEGIN
  221. ASSERT(ValidDateTime(dt));
  222. IF (days > 0) THEN
  223. WHILE (days > 0) DO
  224. nofDaysLeft := NofDays(dt.year, dt.month) - dt.day;
  225. IF (days > nofDaysLeft) THEN
  226. dt.day := 1;
  227. AddMonths(dt, 1);
  228. days := days - nofDaysLeft - 1; (* -1 because we consume the first day of the next month *)
  229. ELSE
  230. dt.day := dt.day + days;
  231. days := 0;
  232. END;
  233. END;
  234. ELSIF (days < 0) THEN
  235. days := -days;
  236. WHILE (days > 0) DO
  237. nofDaysLeft := dt.day - 1;
  238. IF (days > nofDaysLeft) THEN
  239. dt.day := 1; (* otherwise, dt could become an invalid date if the previous month has less days than dt.day *)
  240. AddMonths(dt, -1);
  241. dt.day := NofDays(dt.year, dt.month);
  242. days := days - nofDaysLeft - 1; (* -1 because we consume the last day of the previous month *)
  243. ELSE
  244. dt.day := dt.day - days;
  245. days := 0;
  246. END;
  247. END;
  248. END;
  249. ASSERT(ValidDateTime(dt));
  250. END AddDays;
  251. (** Add/Subtract a number of hours to/from dt. This will adjust dt.day, dt.month and dt.year if necessary *)
  252. PROCEDURE AddHours*(VAR dt : DateTime; hours : LONGINT);
  253. VAR days : LONGINT;
  254. BEGIN
  255. ASSERT(ValidDateTime(dt));
  256. dt.hour := dt.hour + hours;
  257. days := dt.hour DIV 24;
  258. dt.hour := dt.hour MOD 24;
  259. IF (dt.hour < 0) THEN
  260. dt.hour := dt.hour + 24;
  261. DEC(days);
  262. END;
  263. IF (days # 0) THEN AddDays(dt, days); END;
  264. ASSERT(ValidDateTime(dt));
  265. END AddHours;
  266. (** Add/Subtract a number of minutes to/from dt. This will adjust dt.hour, dt.day, dt.month and dt.year if necessary *)
  267. PROCEDURE AddMinutes*(VAR dt : DateTime; minutes : LONGINT);
  268. VAR hours : LONGINT;
  269. BEGIN
  270. ASSERT(ValidDateTime(dt));
  271. dt.minute := dt.minute + minutes;
  272. hours := dt.minute DIV 60;
  273. dt.minute := dt.minute MOD 60;
  274. IF (dt.minute < 0) THEN
  275. dt.minute := dt.minute + 60;
  276. DEC(hours);
  277. END;
  278. IF (hours # 0) THEN AddHours(dt, hours); END;
  279. ASSERT(ValidDateTime(dt));
  280. END AddMinutes;
  281. (** Add/Subtract a number of seconds to/from dt. This will adjust dt.minute, dt.hour, dt.day, dt.month and dt.year if necessary *)
  282. PROCEDURE AddSeconds*(VAR dt : DateTime; seconds : LONGINT);
  283. VAR minutes : LONGINT;
  284. BEGIN
  285. ASSERT(ValidDateTime(dt));
  286. dt.second := dt.second + seconds;
  287. minutes := dt.second DIV 60;
  288. dt.second := dt.second MOD 60;
  289. IF (dt.second < 0) THEN
  290. dt.second := dt.second + 60;
  291. DEC(minutes);
  292. END;
  293. IF (minutes # 0) THEN AddMinutes(dt, minutes); END;
  294. ASSERT(ValidDateTime(dt));
  295. END AddSeconds;
  296. BEGIN
  297. Months[0] := "January"; Months[1] := "February"; Months[2] := "March"; Months[3] := "April"; Months[4] := "May";
  298. Months[5] := "June"; Months[6] := "July"; Months[7] := "August"; Months[8] := "September";
  299. Months[9] := "October"; Months[10] := "November"; Months[11] := "December";
  300. Days[0] := "Monday"; Days[1] := "Tuesday"; Days[2] := "Wednesday"; Days[3] := "Thursday";
  301. Days[4] := "Friday"; Days[5] := "Saturday"; Days[6] := "Sunday";
  302. NoDays[0] := 31; NoDays[1] := 28; NoDays[2] := 31; NoDays[3] := 30; NoDays[4] := 31; NoDays[5] := 30;
  303. NoDays[6] := 31; NoDays[7] := 31; NoDays[8] := 30; NoDays[9] := 31; NoDays[10] := 30; NoDays[11] := 31;
  304. ZeroDateUnix.year:=1970; ZeroDateUnix.month:=1; ZeroDateUnix.day:=1;
  305. ZeroDateRFC868.year:=1900; ZeroDateRFC868.month:=1; ZeroDateRFC868.day:=1;
  306. ZeroDateNTP:=ZeroDateRFC868;
  307. END Dates.