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