def test_to_date(self):
        self.assertEqual(to_date("2000"), datetime.datetime(2000, 1, 1, 0, 0))
        self.assertEqual(to_date("2000-02"), datetime.datetime(2000, 2, 1, 0, 0))
        self.assertEqual(to_date("2000-02-03"), datetime.datetime(2000, 2, 3, 0, 0))
        self.assertEqual(to_date("2001-02", round="up"), datetime.datetime(2001, 2, 28, 0, 0))
        # 2000 is a leap year
        self.assertEqual(to_date("2000-02", round="up"), datetime.datetime(2000, 2, 29, 0, 0))
        self.assertEqual(to_date("2000-12", round="up"), datetime.datetime(2000, 12, 31, 0, 0))

        self.assertEqual(to_date("2000", round="up"), datetime.datetime(2000, 12, 31, 0, 0))
        self.assertEqual(to_date("0200", round="up"), datetime.datetime(200, 12, 31, 0, 0))
        self.assertEqual(to_date("1200"), datetime.datetime(1200, 1, 1, 0, 0))
        def extract_min_max_from_event(type):  # @ReservedAssignment
            """given an event, return a miminal and maximal date

            returns a tuple of two date instances
            """
            event = self.get_event(type)
            if event is not None:
                date_min = date_max = event.get('when')
                if not date_min:
                    date_min = event.get('notBefore')
                if not date_max:
                    date_max = event.get('notAfter')
                try:
                    date_min = to_date(date_min)
                except ValueError:
                    date_min = None
                try:
                    date_max = to_date(date_max, round='up')
                except ValueError:
                    date_max = None
                return (date_min, date_max)
            else:
                return (None, None)
    def save(self):
        with self.repository.db.get_session_context() as session:
            #             r_person = self._get_record(session)
            sources = self.get_sources()
            bioport_id = self.get_bioport_id()
            r_person = self.record
            #             r_person = self._get_record(session)
            # XXX: is this obsolete?
            if getattr(self, "remarks", None) is not None:
                r_person.remarks = self.remarks
            if self.record.status is None:
                r_person.status = STATUS_NEW

            computed_values = self.computed_values
            r_person.naam = computed_values.naam
            r_person.sort_key = computed_values.sort_key
            r_person.has_illustrations = computed_values.has_illustrations
            r_person.search_source = computed_values.search_source
            r_person.sex = computed_values.sex
            r_person.geboortedatum_min = computed_values.geboortedatum_min
            r_person.geboortedatum_max = computed_values.geboortedatum_max
            r_person.sterfdatum_min = computed_values.sterfdatum_min
            r_person.sterfdatum_max = computed_values.sterfdatum_max
            r_person.geboortedatum = computed_values.geboortedatum
            r_person.sterfdatum = computed_values.sterfdatum
            r_person.geboorteplaats = computed_values.geboorteplaats
            r_person.sterfplaats = computed_values.sterfplaats
            r_person.names = computed_values.names
            r_person.snippet = computed_values.snippet
            r_person.has_contradictions = computed_values.has_contradictions
            r_person.thumbnail = computed_values.thumbnail
            # # BB
            #     has_name = Column(Boolean) # if naam != null && != ''
            r_person.has_name = (r_person.naam is not None) and (r_person.naam != "")

            if r_person.geboortedatum_min is not None and r_person.geboortedatum_min == r_person.geboortedatum_max:
                date = to_date(r_person.geboortedatum_min[0:10])
                iso = date.isoformat()
                r_person.birthday = iso[5:7] + iso[8:10]

            if r_person.sterfdatum_min is not None and r_person.sterfdatum_min == r_person.sterfdatum_max:
                date = to_date(r_person.sterfdatum_min[0:10])
                #                 print 'date = %s' % date
                iso = date.isoformat()
                r_person.deathday = iso[5:7] + iso[8:10]

            #     initial = Column(MSString(1), index=True) # eerste letter van naam
            if r_person.has_name:
                lower = r_person.naam.lower()
                try:
                    tmpinit = coerce_to_ascii(lower[0])
                    """ throws exception when first character is non-ascii """
                except:
                    tmpinit = coerce_to_ascii(
                        lower.replace(u"\u0133", "ij").replace(u"ã¼", u"ü").replace(u"\xf8", "o")
                    )[0]
                r_person.initial = tmpinit

            #     invisible = Column(Boolean) #
            non_portrait_sources = [
                source for source in sources if source.id != "bioport" and source.source_type != SOURCE_TYPE_PORTRAITS
            ]
            r_person.invisible = (
                # person.status IN (11, 5, 9, 9999, 14, 15)
                r_person.status in TO_HIDE
                or
                # we also hide persons that are only have only portraits as biographies
                not non_portrait_sources
            )

            #             #     foreigner = Column(Boolean) # person.status IN (11)
            #             r_person.foreigner = r_person.status == STATUS_FOREIGNER
            #     orphan = Column(Boolean) # person is orphan when the only sources linking to it is 'bioport'
            """ TODO: test this"""
            r_person.orphan = bool(len(sources) == 0 or (len(sources) == 1 and sources[0].id == "bioport"))

            # # /BB
            # update categories
            session.query(RelPersonCategory).filter(RelPersonCategory.bioport_id == bioport_id).delete()

            done = []
            merged_biography = self.get_merged_biography()
            for category in merged_biography.get_states(type="category"):
                category_id = category.get("idno")
                assert type(category_id) in [type(u""), type("")], category_id
                try:
                    category_id = int(category_id)
                except ValueError:
                    msg = "%s- %s: %s" % (category_id, etree.tostring(category), self.bioport_id)
                    raise Exception(msg)
                if category_id not in done:
                    r = RelPersonCategory(bioport_id=bioport_id, category_id=category_id)
                    done.append(category_id)
                    session.add(r)
                    session.flush()

            # update the religion table
            religion = merged_biography.get_religion()
            religion_qry = session.query(RelPersonReligion).filter(RelPersonReligion.bioport_id == bioport_id)
            if religion is not None:
                religion_id = religion.get("idno")
                if religion_id:
                    try:
                        r = religion_qry.one()
                        r.religion_id = religion_id
                    except NoResultFound:
                        r = RelPersonReligion(bioport_id=bioport_id, religion_id=religion_id)
                        session.add(r)
                    session.flush()
            else:
                religion_qry.delete()
                session.flush()

            # 'the' source -- we take the first non-bioport source as 'the' source
            # and we use it only for filtering later
            # XXX what is this used for???
            src = [s for s in merged_biography.get_biographies() if s.source_id != "bioport"]
            if src:
                src = src[0].source_id
            else:
                src = None

            # refresh the names
            self.repository.db.delete_names(bioport_id=bioport_id)
            self.repository.db.update_name(bioport_id=bioport_id, names=computed_values._names)

            source_ids = [source.id for source in sources]

            # delete existing references
            session.query(PersonSource).filter(PersonSource.bioport_id == bioport_id).delete()
            for source_id in source_ids:
                r = PersonSource(bioport_id=bioport_id, source_id=source_id)
                session.add(r)
            session.flush()

            if self.get_biography_contradictions():
                r_person.has_contradictions = True
            else:
                r_person.has_contradictions = False

            msg = "Changed person"
            self.repository.db.log(msg, r_person)

        # XXX: these next two lines somehow guarantee that something does not break - find out why, what, and remove them
        with self.repository.db.get_session_context() as session:
            session.merge(self.record)