Package Gnumed :: Package timelinelib :: Package calendar :: Package gregorian :: Module gregorian |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.calendar.gregorian.gregorian

      1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
      2  #
      3  # This file is part of Timeline.
      4  #
      5  # Timeline is free software: you can redistribute it and/or modify
      6  # it under the terms of the GNU General Public License as published by
      7  # the Free Software Foundation, either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # Timeline is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU General Public License
     16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>.
     17
     18
     19  from timelinelib.calendar.gregorian.time import GregorianDelta
     20  from timelinelib.calendar.gregorian.time import GregorianTime
     21
     22
     23 -class GregorianDateTime(object):
     24
     25 -    def __init__(self, year, month, day, hour, minute, second):
     26          if not is_valid(year, month, day):
     27              raise ValueError("Invalid gregorian date %s-%s-%s" % (year, month, day))
     28          self.year = year
     29          self.month = month
     30          self.day = day
     31          self.hour = hour
     32          self.minute = minute
     33          self.second = second
     34
     35 -    def __eq__(self, other):
     36          return (isinstance(other, self.__class__) and
     37                  self.to_tuple() == other.to_tuple())
     38
     39 -    def __ne__(self, other):
     40          return not (self == other)
     41
     42      @classmethod
     43 -    def from_ymd(cls, year, month, day):
     44          return cls(year, month, day, 0, 0, 0)
     45
     46      @classmethod
     47 -    def from_time(cls, time):
     48          (year, month, day) = julian_day_to_gregorian_ymd(time.julian_day)
     49          (hour, minute, second) = time.get_time_of_day()
     50          return cls(year, month, day, hour, minute, second)
     51
     52      @property
     53 -    def week_number(self):
     54          def monday_week_1(year):
     55              from timelinelib.calendar.gregorian.timetype import GregorianTimeType
     56              jan_4 = GregorianDateTime.from_ymd(year, 1, 4).to_time()
     57              jan_4_day_of_week = GregorianTimeType().get_day_of_week(jan_4)
     58              return jan_4 - GregorianDelta.from_days(jan_4_day_of_week)
     59
     60          def days_between(end, start):
     61              return end.julian_day - start.julian_day
     62
     63          def days_since_monday_week_1(time):
     64              year = GregorianDateTime.from_time(time).year
     65              diff = days_between(end=time, start=monday_week_1(year + 1))
     66              if diff >= 0:
     67                  return diff
     68              diff = days_between(end=time, start=monday_week_1(year))
     69              if diff >= 0:
     70                  return diff
     71              diff = days_between(end=time, start=monday_week_1(year - 1))
     72              if diff >= 0:
     73                  return diff
     74              raise ValueError("should not end up here")
     75          return days_since_monday_week_1(self.to_time()) / 7 + 1
     76
     77 -    def is_bc(self):
     78          return self.year <= 0
     79
     80 -    def replace(self, year=None, month=None):
     81          if year is None:
     82              year = self.year
     83          if month is None:
     84              month = self.month
     85          return self.__class__(
     86              year,
     87              month,
     88              self.day,
     89              self.hour,
     90              self.minute,
     91              self.second
     92          )
     93
     94 -    def days_in_month(self):
     95          return days_in_month(self.year, self.month)
     96
     97 -    def to_tuple(self):
     98          return (self.year, self.month, self.day, self.hour, self.minute,
     99                  self.second)
    100
    101 -    def to_date_tuple(self):
    102          return (self.year, self.month, self.day)
    103
    104 -    def to_time_tuple(self):
    105          return (self.hour, self.minute, self.second)
    106
    107 -    def to_time(self):
    108          days = gregorian_ymd_to_julian_day(self.year, self.month, self.day)
    109          seconds = self.hour * 60 * 60 + self.minute * 60 + self.second
    110          return GregorianTime(days, seconds)
    111
    112 -    def is_first_day_in_year(self):
    113          return (self.month == 1 and
    114                  self.day == 1 and
    115                  self.hour == 0 and
    116                  self.minute == 0 and
    117                  self.second == 0)
    118
    119 -    def is_first_of_month(self):
    120          return (self.day == 1 and
    121                  self.hour == 0 and
    122                  self.minute == 0 and
    123                  self.second == 0)
    124
    125 -    def __repr__(self):
    126          return "GregorianDateTime<%d-%02d-%02d %02d:%02d:%02d>" % self.to_tuple()
    127
    128
    129 -def days_in_month(year, month):
    130      if month in [4, 6, 9, 11]:
    131          return 30
    132      if month in [1, 3, 5, 7, 8, 10, 12]:
    133          return 31
    134      if is_leap_year(year):
    135          return 29
    136      return 28
    137
    138
    139 -def is_leap_year(year):
    140      return year % 4 == 0 and (year % 400 == 0 or not year % 100 == 0)
    141
    142
    143 -def is_valid_time(hour, minute, second):
    144      return (
    145          hour >= 0 and hour < 24 and
    146          minute >= 0 and minute < 60 and
    147          second >= 0 and second < 60
    148      )
    149
    150
    151 -def is_valid(year, month, day):
    152      return (
    153          month >= 1 and month <= 12 and
    154          day >= 1 and day <= days_in_month(year, month)
    155      )
    156
    157
    158 -def julian_day_to_gregorian_ymd(julian_day):
    159      """
    160      This algorithm is described here:
    161
    162      * http://www.tondering.dk/claus/cal/julperiod.php#formula
    163
    164      Integer division works differently in C and in Python for negative numbers.
    165      C truncates towards 0 and Python truncates towards negative infinity:
    166      http://python-history.blogspot.se/2010/08/why-pythons-integer-division-floors.html
    167
    168      The above source don't state which to be used. If we can prove that
    169      division-expressions are always positive, we can be sure this algorithm
    170      works the same in C and in Python.
    171
    172      We must prove that:
    173
    174      1) m             >= 0
    175      2) ((5 * e) + 2) >= 0  =>  e >= 0
    176      3) (1461 * d)    >= 0  =>  d >= 0
    177      4) ((4 * c) + 3) >= 0  =>  c >= 0
    178      5) (b * 146097)  >= 0  =>  b >= 0
    179      6) ((4 * a) + 3) >= 0  =>  a >= 0
    180
    181      Let's work from the top:
    182
    183      julian_day >= 0                   =>
    184
    185      a >= 0 + 32044
    186         = 32044                        =>
    187
    188      This proves 6).
    189
    190      b >= ((4 * 32044) + 3) // 146097
    191         = 0
    192
    193      This proves 5).
    194
    195      Let's look at c:
    196
    197      c = a - ((b * 146097) // 4)
    198        = a - (((((4 * a) + 3) // 146097) * 146097) // 4)
    199
    200      For c to be >= 0, then
    201
    202      (((((4 * a) + 3) // 146097) * 146097) // 4) <= a
    203
    204      Let's look at this component: ((((4 * a) + 3) // 146097) * 146097)
    205
    206      This expression can never be larger than (4 * a) + 3. That gives this:
    207
    208      ((4 * a) + 3) // 4 <= a, which holds.
    209
    210      This proves 4).
    211
    212      Now, let's look at d:
    213
    214      d = ((4 * c) + 3) // 1461
    215
    216      If c is >= 0, then d is also >= 0.
    217
    218      This proves 3).
    219
    220      Let's look at e:
    221
    222      e = c - ((1461 * d) // 4)
    223        = c - ((1461 * (((4 * c) + 3) // 1461)) // 4)
    224
    225      The same resoning as above can be used to conclude that e >= 0.
    226
    227      This proves 2).
    228
    229      Now, let's look at m:
    230
    231      m = ((5 * e) + 2) // 153
    232
    233      If e >= 0, then m is also >= 0.
    234
    235      This proves 1).
    236      """
    237      if julian_day < GregorianTime.MIN_JULIAN_DAY:
    238          raise ValueError("julian_day_to_gregorian_ymd only works for julian days >= %d, but was %d" % (GregorianTime.MIN_JULIAN_DAY, julian_day))
    239      a = julian_day + 32044
    240      b = ((4 * a) + 3) // 146097
    241      c = a - ((b * 146097) // 4)
    242      d = ((4 * c) + 3) // 1461
    243      e = c - ((1461 * d) // 4)
    244      m = ((5 * e) + 2) // 153
    245      day = e - (((153 * m) + 2) // 5) + 1
    246      month = m + 3 - (12 * (m // 10))
    247      year = (b * 100) + d - 4800 + (m // 10)
    248      return (year, month, day)
    249
    250
    251 -def gregorian_ymd_to_julian_day(year, month, day):
    252      """
    253      This algorithm is described here:
    254
    255      * http://www.tondering.dk/claus/cal/julperiod.php#formula
    256      * http://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_Day_Number
    257
    258      Integer division works differently in C and in Python for negative numbers.
    259      C truncates towards 0 and Python truncates towards negative infinity:
    260      http://python-history.blogspot.se/2010/08/why-pythons-integer-division-floors.html
    261
    262      The above sources don't state which to be used. If we can prove that
    263      division-expressions are always positive, we can be sure this algorithm
    264      works the same in C and in Python.
    265
    266      We must prove that:
    267
    268      1) y >= 0
    269      2) ((153 * m) + 2) >= 0
    270
    271      Let's prove 1):
    272
    273      y = year + 4800 - a
    274        = year + 4800 - ((14 - month) // 12)
    275
    276      year >= -4713 (gives a julian day of 0)
    277
    278      so
    279
    280      year + 4800 >= -4713 + 4800 = 87
    281
    282      The expression ((14 - month) // 12) varies between 0 and 1 when month
    283      varies between 1 and 12. Therefore y >= 87 - 1 = 86, and 1) is proved.
    284
    285      Let's prove 2):
    286
    287      m = month + (12 * a) - 3
    288        = month + (12 * ((14 - month) // 12)) - 3
    289
    290      1 <= month <= 12
    291
    292      m(1)  = 1  + (12 * ((14 - 1)  // 12)) - 3 = 1  + (12 * 1) - 3 = 10
    293      m(2)  = 2  + (12 * ((14 - 2)  // 12)) - 3 = 2  + (12 * 1) - 3 = 11
    294      m(3)  = 3  + (12 * ((14 - 3)  // 12)) - 3 = 3  + (12 * 0) - 3 = 0
    295      m(4)  = 4  + (12 * ((14 - 4)  // 12)) - 3 = 4  + (12 * 0) - 3 = 1
    296      m(5)  = 5  + (12 * ((14 - 5)  // 12)) - 3 = 5  + (12 * 0) - 3 = 2
    297      m(6)  = 6  + (12 * ((14 - 6)  // 12)) - 3 = 6  + (12 * 0) - 3 = 3
    298      m(7)  = 7  + (12 * ((14 - 7)  // 12)) - 3 = 7  + (12 * 0) - 3 = 4
    299      m(8)  = 8  + (12 * ((14 - 8)  // 12)) - 3 = 8  + (12 * 0) - 3 = 5
    300      m(9)  = 9  + (12 * ((14 - 9)  // 12)) - 3 = 9  + (12 * 0) - 3 = 6
    301      m(10) = 10 + (12 * ((14 - 10) // 12)) - 3 = 10 + (12 * 0) - 3 = 7
    302      m(11) = 11 + (12 * ((14 - 11) // 12)) - 3 = 11 + (12 * 0) - 3 = 8
    303      m(12) = 12 + (12 * ((14 - 12) // 12)) - 3 = 12 + (12 * 0) - 3 = 9
    304
    305      So, m is always > 0. Which also makes the expression ((153 * m) + 2) > 0,
    306      and 2) is proved.
    307      """
    308      a = (14 - month) // 12
    309      y = year + 4800 - a
    310      m = month + (12 * a) - 3
    311      julian_day = (day
    312                    + (((153 * m) + 2) // 5)
    313                    + (y * 365)
    314                    + (y // 4)
    315                    - (y // 100)
    316                    + (y // 400)
    317                    - 32045)
    318      if julian_day < GregorianTime.MIN_JULIAN_DAY:
    319          raise ValueError("gregorian_ymd_to_julian_day only works for julian days >= %d, but was %d" % (GregorianTime.MIN_JULIAN_DAY, julian_day))
    320      return julian_day
    321