]> git.phdru.name Git - m_lib.git/blob - m_lib/opdate.py
Use integer division for Py3 compatibility
[m_lib.git] / m_lib / opdate.py
1 #! /usr/bin/env python
2 # -*- coding: koi8-r -*-
3
4 #
5 # opDate - date/time manipulation routines
6 # Some ideas came from Turbo Professional/Object Professional (t/o)pDate.PAS
7 #
8
9
10 from __future__ import print_function
11 from string import *
12 from time import *
13 from calendar import *
14 from opstring import *
15
16
17 MinYear = 1600
18 MaxYear = 3999
19 MinDate = 0x00000000 # = 01/01/1600
20 MaxDate = 0x000D6025 # = 12/31/3999
21 Date1900 = 0x0001AC05 # = 01/01/1900
22 Date1980 = 0x00021E28 # = 01/01/1980
23 Date2000 = 0x00023AB1 # = 01/01/2000
24 BadDate = 0x7FFFFFFF
25
26 Threshold2000 = 1900
27
28 MinTime = 0          # = 00:00:00 am
29 MaxTime = 86399      # = 23:59:59 pm
30 BadTime = 0x7FFFFFFF
31
32 SecondsInDay = 86400 # number of seconds in a day
33 SecondsInHour = 3600 # number of seconds in an hour
34 SecondsInMinute = 60 # number of seconds in a minute
35 HoursInDay = 24      # number of hours in a day
36 MinutesInHour = 60   # number of minutes in an hour
37
38 First2Months = 59    # 1600 was a leap year
39 FirstDayOfWeek = 5   # 01/01/1600 was a Saturday
40
41
42 # Errors
43 class opdate_error(Exception):
44    pass
45
46
47 #
48 ### Date manipulation routines
49 #
50
51 def IsLeapYear(Year):
52    if ( (Year % 4 == 0) and (Year % 4000 != 0) and ((Year % 100 != 0) or (Year % 400 == 0)) ):
53       return True
54    return False
55
56
57 def _setYear(Year):
58    # Internal function
59    if Year < 100:
60       Year = Year + 1900
61       if Year < Threshold2000:
62          Year = Year + 100
63    return Year
64
65
66 def DaysInMonth(Month, Year):
67    """ Return the number of days in the specified month of a given year """
68    if Month in [1, 3, 5, 7, 8, 10, 12]:
69       return 31
70
71    elif Month in [4, 6, 9, 11]:
72       return 30
73
74    elif Month == 2:
75       return 28+IsLeapYear(_setYear(Year))
76
77    else:
78       raise opdate_error("bad month `%s'" % str(Month))
79
80
81 def ValidDate(Day, Month, Year):
82    """ Verify that day, month, year is a valid date """
83    Year = _setYear(Year)
84
85    if (Day < 1) or (Year < MinYear) or (Year > MaxYear):
86       return False
87    elif (Month >= 1) and (Month <= 12):
88          return Day <= DaysInMonth(Month, Year)
89    else:
90       return False
91
92
93 def DMYtoDate(Day, Month, Year):
94    """ Convert from day, month, year to a julian date """
95    Year = _setYear(Year)
96
97    if not ValidDate(Day, Month, Year):
98       return BadDate
99
100    if (Year == MinYear) and (Month < 3):
101       if Month == 1:
102          return Day-1
103       else:
104          return Day+30
105    else:
106       if Month > 2:
107          Month = Month - 3
108       else:
109          Month = Month + 9
110          Year = Year - 1
111       Year = Year - MinYear
112
113       return (((Year // 100)*146097) // 4) + (((Year % 100)*1461) // 4) + (((153*Month)+2) // 5)+Day+First2Months
114
115
116 def DateToDMY(Julian):
117    """ Convert from a julian date to day, month, year """
118    if Julian == BadDate:
119       return 0, 0, 0
120
121    if Julian <= First2Months:
122       Year = MinYear
123       if Julian <= 30:
124          Month = 1
125          Day = Julian + 1
126       else:
127          Month = 2
128          Day = Julian-30
129    else:
130       I = (4*(Julian-First2Months))-1
131       J = (4*((I % 146097) // 4))+3
132       Year = (100*(I // 146097))+(J // 1461)
133       I = (5*(((J % 1461)+4) // 4))-3
134       Month = I // 153
135       Day = ((I % 153)+5) // 5
136       if Month < 10:
137          Month = Month + 3
138       else:
139          Month = Month - 9
140          Year = Year + 1
141       Year = Year + MinYear
142
143    return Day, Month, Year
144
145
146 def IncDate(Julian, Days, Months, Years):
147    """ Add (or subtract) the number of months, days, and years to a date.
148        Months and years are added before days. No overflow/underflow checks are made
149    """
150    Day, Month, Year = DateToDMY(Julian)
151    Day28Delta = Day-28
152    if Day28Delta < 0:
153       Day28Delta = 0
154    else:
155       Day = 28
156
157    Year = Year + Years
158    Year = Year + Months // 12
159    Month = Month + Months % 12
160    if Month < 1:
161       Month = Month + 12
162       Year = Year - 1
163    elif Month > 12:
164       Month = Month - 12
165       Year = Year + 1
166
167    Julian = DMYtoDate(Day, Month, Year)
168    if Julian != BadDate:
169       Julian = Julian + Days + Day28Delta
170
171    return Julian
172
173
174 def IncDateTrunc(Julian, Months, Years):
175    """ Add (or subtract) the specified number of months and years to a date """
176    Day, Month, Year = DateToDMY(Julian)
177    Day28Delta = Day-28
178    if Day28Delta < 0:
179       Day28Delta = 0
180    else:
181       Day = 28
182
183    Year = Year + Years
184    Year = Year + Months // 12
185    Month = Month + Months % 12
186    if Month < 1:
187       Month = Month + 12
188       Year = Year - 1
189    elif Month > 12:
190       Month = Month - 12
191       Year = Year + 1
192
193    Julian = DMYtoDate(Day, Month, Year)
194    if Julian != BadDate:
195       MaxDay = DaysInMonth(Month, Year)
196       if Day+Day28Delta > MaxDay:
197          Julian = Julian + MaxDay-Day
198       else:
199          Julian = Julian + Day28Delta
200
201    return Julian
202
203
204 def DateDiff(Date1, Date2):
205    """ Return the difference in days,months,years between two valid julian dates """
206    #we want Date2 > Date1
207    if Date1 > Date2:
208       _tmp = Date1
209       Date1 = Date2
210       Date2 = _tmp
211
212    #convert dates to day,month,year
213    Day1, Month1, Year1 = DateToDMY(Date1)
214    Day2, Month2, Year2 = DateToDMY(Date2)
215
216    #days first
217    if Day2 < Day1:
218       Month2 = Month2 - 1
219       if Month2 == 0:
220          Month2 = 12
221          Year2 = Year2 - 1
222       Day2 = Day2 + DaysInMonth(Month2, Year2)
223    Days = abs(Day2-Day1)
224
225    #now months and years
226    if Month2 < Month1:
227       Month2 = Month2 + 12
228       Year2 = Year2 - 1
229    Months = Month2-Month1
230    Years = Year2-Year1
231
232    return Days, Months, Years
233
234
235 def DayOfWeek(Julian):
236    """ Return the day of the week for the date. Returns DayType(7) if Julian == BadDate. """
237    if Julian == BadDate:
238       raise opdate_error("bad date `%s'" % str(Julian))
239    else:
240       return (Julian+FirstDayOfWeek) % 7
241
242
243 def DayOfWeekDMY(Day, Month, Year):
244    """ Return the day of the week for the day, month, year """
245    return DayOfWeek( DMYtoDate(Day, Month, Year) )
246
247
248 #def MonthStringToMonth(MSt):
249 #   """ Convert the month name in MSt to a month (1..12) or -1 on error """
250 #   lmn = strptime.LongMonthNames[strptime.LANGUAGE]
251 #   smn = strptime.ShortMonthNames[strptime.LANGUAGE]
252 #   lmna = LongMonthNamesA
253 #
254 #   I = FindStr(MSt, lmn)+1 or FindStr(MSt, smn)+1 or \
255 #      FindStrUC(MSt, lmn)+1 or FindStrUC(MSt, smn)+1 or \
256 #      FindStr(MSt, lmna)+1 or FindStrUC(MSt, lmna)+1
257 #
258 #   return I-1
259
260
261 def Today():
262    """ Returns today's date as a julian """
263    Year, Month, Day = localtime(time())[0:3]
264    return DMYtoDate(Day, Month, Year)
265
266 #
267 ### Time manipulation routines
268 #
269
270 def TimeToHMS(T):
271    """ Convert a Time variable to Hours, Minutes, Seconds """
272    if T == BadTime:
273       return 0, 0, 0
274
275    else:
276       Hours = T // SecondsInHour
277       T = T - Hours*SecondsInHour
278       Minutes = T // SecondsInMinute
279       T = T - Minutes*SecondsInMinute
280       Seconds = T
281
282       return Hours, Minutes, Seconds
283
284
285 def HMStoTime(Hours, Minutes, Seconds):
286    """ Convert Hours, Minutes, Seconds to a Time variable """
287    Hours = Hours % HoursInDay
288    T = Hours*SecondsInHour + Minutes*SecondsInMinute + Seconds
289
290    return T % SecondsInDay
291
292
293 def ValidTime(Hours, Minutes, Seconds):
294    """ Return true if Hours:Minutes:Seconds is a valid time """
295    return (0 <= Hours < 24) and (0 <= Minutes < 60) and (0 <= Seconds < 60)
296
297
298 def CurrentTime():
299    """ Returns current time in seconds since midnight """
300    Hours, Minutes, Seconds = localtime(time())[3:6]
301    return HMStoTime(Hours, Minutes, Seconds)
302
303
304 def TimeDiff(Time1, Time2):
305    """ Return the difference in hours,minutes,seconds between two times """
306    if Time1 > Time2:
307       T = Time1-Time2
308    else:
309       T = Time2-Time1
310
311    Hours, Minutes, Seconds = TimeToHMS(T)
312    return Hours, Minutes, Seconds
313
314
315 def IncTime(T, Hours, Minutes, Seconds):
316    """ Add the specified hours,minutes,seconds to T and return the result """
317    T = T + HMStoTime(Hours, Minutes, Seconds)
318    return T % SecondsInDay
319
320
321 def DecTime(T, Hours, Minutes, Seconds):
322    """ Subtract the specified hours,minutes,seconds from T and return the result """
323    Hours = Hours % HoursInDay
324    T = T - HMStoTime(Hours, Minutes, Seconds)
325    if T < 0:
326       return T+SecondsInDay
327    else:
328       return T
329
330
331 def RoundToNearestHour(T, Truncate = False):
332    """ Round T to the nearest hour, or Truncate minutes and seconds from T """
333    Hours, Minutes, Seconds = TimeToHMS(T)
334    Seconds = 0
335
336    if not Truncate:
337       if Minutes >= (MinutesInHour // 2):
338          Hours = Hours + 1
339
340    Minutes = 0
341    return HMStoTime(Hours, Minutes, Seconds)
342
343
344 def RoundToNearestMinute(T, Truncate = False):
345    """ Round T to the nearest minute, or Truncate seconds from T """
346    Hours, Minutes, Seconds = TimeToHMS(T)
347
348    if not Truncate:
349       if Seconds >= (SecondsInMinute // 2):
350          Minutes = Minutes + 1
351
352    Seconds = 0
353    return HMStoTime(Hours, Minutes, Seconds)
354
355
356 def DateTimeDiff(DT1, DT2):
357    """ Return the difference in days,seconds between two points in time """
358    # swap if DT1 later than DT2
359    if (DT1[0] > DT2[0]) or ((DT1[0] == DT2[0]) and (DT1[1] > DT2[1])):
360       _tmp = DT1
361       DT1 = DT2
362       DT2 = _tmp
363
364    # the difference in days is easy
365    Days = DT2[0]-DT1[0]
366
367    # difference in seconds
368    if DT2[1] < DT1[1]:
369       # subtract one day, add 24 hours
370       Days = Days - 1
371       DT2[1] = DT2[1] + SecondsInDay
372
373    Secs = DT2[1]-DT1[1]
374    return Days, Secs
375
376
377 def IncDateTime(DT1, Days, Secs):
378    """ Increment (or decrement) DT1 by the specified number of days and seconds
379       and put the result in DT2 """
380    DT2 = DT1[:]
381
382    # date first
383    DT2[0] = DT2[0] + Days
384
385    if Secs < 0:
386       # change the sign
387       Secs = -Secs
388
389       # adjust the date
390       DT2[0] = DT2[0] - Secs // SecondsInDay
391       Secs = Secs % SecondsInDay
392
393       if Secs > DT2[1]:
394          # subtract a day from DT2[0] and add a day's worth of seconds to DT2[1]
395          DT2[0] = DT2[0] - 1
396          DT2[1] = DT2[1] + SecondsInDay
397
398       # now subtract the seconds
399       DT2[1] = DT2[1] - Secs
400
401    else:
402       # increment the seconds
403       DT2[1] = DT2[1] + Secs
404
405       # adjust date if necessary
406       DT2[0] = DT2[0] + DT2[1] // SecondsInDay
407
408       # force time to 0..SecondsInDay-1 range
409       DT2[1] = DT2[1] % SecondsInDay
410
411    return DT2
412
413
414 #
415 ### UTC (GMT) stuff
416 #
417
418 UTC_0Date = DMYtoDate(1, 1, 1970)
419
420
421 def DateTimeToGMT(Date, Time = False):
422    Date = Date - UTC_0Date
423    return Date*SecondsInDay + Time
424
425
426 def GMTtoDateTime(GMT):
427    q, r = divmod(GMT, SecondsInDay)
428    return q + UTC_0Date, r
429
430
431 #
432 ### Cyrillic stuff
433 #
434
435 LongMonthNamesA = ['Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня',
436    'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря']
437
438
439 #
440 ### Test stuff
441 #
442
443 def test():
444    print("Is 1984 leap year?", IsLeapYear(1984))
445    print("Is 1990 leap year?", IsLeapYear(1990))
446
447    print("Days in month 8 year 1996:", DaysInMonth(8, 1996))
448
449    print("Is date 8/12/1996 valid?", ValidDate(8, 12, 1996))
450    print("Is date 40/11/1996 valid?", ValidDate(40, 11, 1996))
451    print("Is date 8/14/1996 valid?", ValidDate(8, 14, 1996))
452
453    print("Date->DMY for 138219:", DateToDMY(138219))
454
455    diff = DateDiff(DMYtoDate(12, 10, 1996), DMYtoDate(12, 10, 1997))
456    print("Date 12/10/1996 and date 12/10/1997 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]))
457
458    diff = DateDiff(DMYtoDate(12, 10, 1996), DMYtoDate(12, 11, 1997))
459    print("Date 12/10/1996 and date 12/11/1997 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]))
460
461    diff = DateDiff(DMYtoDate(31, 1, 1996), DMYtoDate(1, 3, 1996))
462    print("Date 31/01/1996 and date 01/03/1996 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]))
463
464
465    #print("November is %dth month" % MonthStringToMonth("November"))
466
467    print("Today is", Today())
468    print("Now is", CurrentTime())
469
470    print("My birthday 21 Dec 1967 is (must be Thursday):", day_name[DayOfWeekDMY(21, 12, 67)])
471
472    gmt = DateTimeToGMT(DMYtoDate(21, 12, 1967), HMStoTime(23, 45, 0))
473    # DOS version of gmtime has error processing dates before 1/1/1970 :(
474    print("21 Dec 1967, 23:45:00 --", gmtime(gmt))
475    D, T = GMTtoDateTime(gmt)
476    print("(gmt) --", DateToDMY(D), TimeToHMS(T))
477
478 if __name__ == "__main__":
479    test()