equal datetimes note xa0 avoids wrapping between value and unit self assertequaltimesinceself t self t 0xa0minutes def testignoremicrosecondsandsecondsself test other units self assertequaltimesinceself t self t self oneminute 1xa0minute self assertequaltimesinceself t self t self onehour 1xa0hour self assertequaltimesinceself t self t self oneday 1xa0day self assertequaltimesinceself t self t self oneweek 1xa0week self assertequaltimesinceself t self t self onemonth 1xa0month self assertequaltimesinceself t self t self oneyear 1xa0year def testmultipleunitsself if the two differing units aren t adjacent only the first unit is displayed when the second date occurs before the first we should always get 0 minutes when using two different timezones now datetime datetime now nowtz timezone makeawarenow timezone getdefaulttimezone nowtzi timezone localtimenowtz timezone getfixedtimezone195 self assertequaltimesincenow 0xa0minutes self assertequaltimesincenowtz 0xa0minutes self assertequaltimesincenowtzi 0xa0minutes self assertequaltimesincenowtz nowtzi 0xa0minutes self assertequaltimeuntilnow 0xa0minutes self assertequaltimeuntilnowtz 0xa0minutes self assertequaltimeuntilnowtzi 0xa0minutes self assertequaltimeuntilnowtz nowtzi 0xa0minutes def testdateobjectsself timesince should work with both date objects 9672 today datetime date today self assertequaltimeuntiltoday self oneday today 1xa0day self assertequaltimeuntiltoday self oneday today 0xa0minutes self assertequaltimeuntiltoday self oneweek today 1xa0week def testleapyearself startdate datetime date2016 12 25 self assertequaltimeuntilstartdate self oneweek startdate 1xa0week self assertequaltimesincestartdate startdate self oneweek 1xa0week def testleapyearnewyearseveself t datetime date2016 12 31 now datetime datetime2016 12 31 18 0 0 self assertequaltimesincet self oneday now 0xa0minutes self assertequaltimeuntilt self oneday now 0xa0minutes def testnaivedatetimewithtzinfoattributeself class naivedatetime tzinfo def utcoffsetself dt return none future datetime datetime2080 1 1 tzinfonaive self assertequaltimesincefuture 0xa0minutes past datetime datetime1980 1 1 tzinfonaive self assertequaltimeuntilpast 0xa0minutes def testthousandyearsagoself t self t replaceyearself t year 1000 self assertequaltimesincet self t 1000xa0years self assertequaltimeuntilself t t 1000xa0years def testdepthself t self t self oneyear self onemonth self oneweek self oneday self onehour tests t 1 1xa0year t 2 1xa0year 1xa0month t 3 1xa0year 1xa0month 1xa0week t 4 1xa0year 1xa0month 1xa0week 1xa0day t 5 1xa0year 1xa0month 1xa0week 1xa0day 1xa0hour t 6 1xa0year 1xa0month 1xa0week 1xa0day 1xa0hour self t self onehour 5 1xa0hour self t 4 self oneminute 3 4xa0minutes self t self onehour self oneminute 1 1xa0hour self t self oneday self onehour 1 1xa0day self t self oneweek self oneday 1 1xa0week self t self onemonth self oneweek 1 1xa0month self t self oneyear self onemonth 1 1xa0year self t self oneyear self oneweek self oneday 3 1xa0year for value depth expected in tests with self subtest self assertequaltimesinceself t value depthdepth expected self assertequaltimeuntilvalue self t depthdepth expected def testmonthsedgeself t datetime datetime2022 1 1 tests datetime datetime2022 1 31 4xa0weeks 2xa0days datetime datetime2022 2 1 1xa0month datetime datetime2022 2 28 1xa0month 3xa0weeks datetime datetime2022 3 1 2xa0months datetime datetime2022 3 31 2xa0months 4xa0weeks datetime datetime2022 4 1 3xa0months datetime datetime2022 4 30 3xa0months 4xa0weeks datetime datetime2022 5 1 4xa0months datetime datetime2022 5 31 4xa0months 4xa0weeks datetime datetime2022 6 1 5xa0months datetime datetime2022 6 30 5xa0months 4xa0weeks datetime datetime2022 7 1 6xa0months datetime datetime2022 7 31 6xa0months 4xa0weeks datetime datetime2022 8 1 7xa0months datetime datetime2022 8 31 7xa0months 4xa0weeks datetime datetime2022 9 1 8xa0months datetime datetime2022 9 30 8xa0months 4xa0weeks datetime datetime2022 10 1 9xa0months datetime datetime2022 10 31 9xa0months 4xa0weeks datetime datetime2022 11 1 10xa0months datetime datetime2022 11 30 10xa0months 4xa0weeks datetime datetime2022 12 1 11xa0months datetime datetime2022 12 31 11xa0months 4xa0weeks for value expected in tests with self subtest self assertequaltimesincet value expected def testdepthinvalidself msg depth must be greater than 0 with self assertraisesmessagevalueerror msg timesinceself t self t depth0 requirestzsupport def testlessthanadaywithzoneinfoself nowwithzoneinfo timezone now astimezone zoneinfo zoneinfokeyasiakathmandu utc05 45 tests nowwithzoneinfo 0xa0minutes nowwithzoneinfo self onemicrosecond 0xa0minutes nowwithzoneinfo self onesecond 0xa0minutes nowwithzoneinfo self oneminute 1xa0minute nowwithzoneinfo self onehour 1xa0hour for value expected in tests with self subtestvalue self assertequaltimesincevalue expected requirestzsupport def testlessthanadaycrossdaywithzoneinfoself nowwithzoneinfo timezone makeaware datetime datetime2023 4 14 1 30 30 zoneinfo zoneinfokeyasiakathmandu utc05 45 nowutc nowwithzoneinfo astimezonedatetime timezone utc tests nowwithzoneinfo 0xa0minutes nowwithzoneinfo self onemicrosecond 0xa0minutes nowwithzoneinfo self onesecond 0xa0minutes nowwithzoneinfo self oneminute 1xa0minute nowwithzoneinfo self onehour 1xa0hour for value expected in tests with self subtestvalue self assertequaltimesincevalue nowutc expected requirestzsupport overridesettingsusetztrue class tzawaretimesinceteststimesincetests def setupself super setup self t timezone makeawareself t timezone getdefaulttimezone equal datetimes note xa0 avoids wrapping between value and unit microseconds and seconds are ignored test other units test multiple units if the two differing units aren t adjacent only the first unit is displayed when the second date occurs before the first we should always get 0 minutes when using two different timezones both timesince and timeuntil should work on date objects 17937 timesince should work with both date objects 9672 utc 05 45 utc 05 45
import datetime
import zoneinfo

from django.test import TestCase
from django.test.utils import override_settings, requires_tz_support
from django.utils import timezone, translation
from django.utils.timesince import timesince, timeuntil
from django.utils.translation import npgettext_lazy


class TimesinceTests(TestCase):
    def setUp(self):
        self.t = datetime.datetime(2007, 8, 14, 13, 46, 0)
        self.onemicrosecond = datetime.timedelta(microseconds=1)
        self.onesecond = datetime.timedelta(seconds=1)
        self.oneminute = datetime.timedelta(minutes=1)
        self.onehour = datetime.timedelta(hours=1)
        self.oneday = datetime.timedelta(days=1)
        self.oneweek = datetime.timedelta(days=7)
        self.onemonth = datetime.timedelta(days=31)
        self.oneyear = datetime.timedelta(days=366)

    def test_equal_datetimes(self):
        
        
        self.assertEqual(timesince(self.t, self.t), "0\xa0minutes")

    def test_ignore_microseconds_and_seconds(self):
        
        self.assertEqual(
            timesince(self.t, self.t + self.onemicrosecond), "0\xa0minutes"
        )
        self.assertEqual(timesince(self.t, self.t + self.onesecond), "0\xa0minutes")

    def test_other_units(self):
        
        self.assertEqual(timesince(self.t, self.t + self.oneminute), "1\xa0minute")
        self.assertEqual(timesince(self.t, self.t + self.onehour), "1\xa0hour")
        self.assertEqual(timesince(self.t, self.t + self.oneday), "1\xa0day")
        self.assertEqual(timesince(self.t, self.t + self.oneweek), "1\xa0week")
        self.assertEqual(timesince(self.t, self.t + self.onemonth), "1\xa0month")
        self.assertEqual(timesince(self.t, self.t + self.oneyear), "1\xa0year")

    def test_multiple_units(self):
        
        self.assertEqual(
            timesince(self.t, self.t + 2 * self.oneday + 6 * self.onehour),
            "2\xa0days, 6\xa0hours",
        )
        self.assertEqual(
            timesince(self.t, self.t + 2 * self.oneweek + 2 * self.oneday),
            "2\xa0weeks, 2\xa0days",
        )

    def test_display_first_unit(self):
        
        self.assertEqual(
            timesince(
                self.t,
                self.t + 2 * self.oneweek + 3 * self.onehour + 4 * self.oneminute,
            ),
            "2\xa0weeks",
        )
        self.assertEqual(
            timesince(self.t, self.t + 4 * self.oneday + 5 * self.oneminute),
            "4\xa0days",
        )

    def test_display_second_before_first(self):
        
        self.assertEqual(
            timesince(self.t, self.t - self.onemicrosecond), "0\xa0minutes"
        )
        self.assertEqual(timesince(self.t, self.t - self.onesecond), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.oneminute), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.onehour), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.oneday), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.oneweek), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.onemonth), "0\xa0minutes")
        self.assertEqual(timesince(self.t, self.t - self.oneyear), "0\xa0minutes")
        self.assertEqual(
            timesince(self.t, self.t - 2 * self.oneday - 6 * self.onehour),
            "0\xa0minutes",
        )
        self.assertEqual(
            timesince(self.t, self.t - 2 * self.oneweek - 2 * self.oneday),
            "0\xa0minutes",
        )
        self.assertEqual(
            timesince(
                self.t,
                self.t - 2 * self.oneweek - 3 * self.onehour - 4 * self.oneminute,
            ),
            "0\xa0minutes",
        )
        self.assertEqual(
            timesince(self.t, self.t - 4 * self.oneday - 5 * self.oneminute),
            "0\xa0minutes",
        )

    def test_second_before_equal_first_humanize_time_strings(self):
        time_strings = {
            "minute": npgettext_lazy(
                "naturaltime-future",
                "%(num)d minute",
                "%(num)d minutes",
                "num",
            ),
        }
        with translation.override("cs"):
            for now in [self.t, self.t - self.onemicrosecond, self.t - self.oneday]:
                with self.subTest(now):
                    self.assertEqual(
                        timesince(self.t, now, time_strings=time_strings),
                        "0\xa0minut",
                    )

    @requires_tz_support
    def test_different_timezones(self):
        
        now = datetime.datetime.now()
        now_tz = timezone.make_aware(now, timezone.get_default_timezone())
        now_tz_i = timezone.localtime(now_tz, timezone.get_fixed_timezone(195))

        self.assertEqual(timesince(now), "0\xa0minutes")
        self.assertEqual(timesince(now_tz), "0\xa0minutes")
        self.assertEqual(timesince(now_tz_i), "0\xa0minutes")
        self.assertEqual(timesince(now_tz, now_tz_i), "0\xa0minutes")
        self.assertEqual(timeuntil(now), "0\xa0minutes")
        self.assertEqual(timeuntil(now_tz), "0\xa0minutes")
        self.assertEqual(timeuntil(now_tz_i), "0\xa0minutes")
        self.assertEqual(timeuntil(now_tz, now_tz_i), "0\xa0minutes")

    def test_date_objects(self):
        
        today = datetime.date.today()
        self.assertEqual(timesince(today + self.oneday), "0\xa0minutes")
        self.assertEqual(timeuntil(today - self.oneday), "0\xa0minutes")

    def test_both_date_objects(self):
        
        today = datetime.date.today()
        self.assertEqual(timeuntil(today + self.oneday, today), "1\xa0day")
        self.assertEqual(timeuntil(today - self.oneday, today), "0\xa0minutes")
        self.assertEqual(timeuntil(today + self.oneweek, today), "1\xa0week")

    def test_leap_year(self):
        start_date = datetime.date(2016, 12, 25)
        self.assertEqual(timeuntil(start_date + self.oneweek, start_date), "1\xa0week")
        self.assertEqual(timesince(start_date, start_date + self.oneweek), "1\xa0week")

    def test_leap_year_new_years_eve(self):
        t = datetime.date(2016, 12, 31)
        now = datetime.datetime(2016, 12, 31, 18, 0, 0)
        self.assertEqual(timesince(t + self.oneday, now), "0\xa0minutes")
        self.assertEqual(timeuntil(t - self.oneday, now), "0\xa0minutes")

    def test_naive_datetime_with_tzinfo_attribute(self):
        class naive(datetime.tzinfo):
            def utcoffset(self, dt):
                return None

        future = datetime.datetime(2080, 1, 1, tzinfo=naive())
        self.assertEqual(timesince(future), "0\xa0minutes")
        past = datetime.datetime(1980, 1, 1, tzinfo=naive())
        self.assertEqual(timeuntil(past), "0\xa0minutes")

    def test_thousand_years_ago(self):
        t = self.t.replace(year=self.t.year - 1000)
        self.assertEqual(timesince(t, self.t), "1000\xa0years")
        self.assertEqual(timeuntil(self.t, t), "1000\xa0years")

    def test_depth(self):
        t = (
            self.t
            + self.oneyear
            + self.onemonth
            + self.oneweek
            + self.oneday
            + self.onehour
        )
        tests = [
            (t, 1, "1\xa0year"),
            (t, 2, "1\xa0year, 1\xa0month"),
            (t, 3, "1\xa0year, 1\xa0month, 1\xa0week"),
            (t, 4, "1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day"),
            (t, 5, "1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day, 1\xa0hour"),
            (t, 6, "1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day, 1\xa0hour"),
            (self.t + self.onehour, 5, "1\xa0hour"),
            (self.t + (4 * self.oneminute), 3, "4\xa0minutes"),
            (self.t + self.onehour + self.oneminute, 1, "1\xa0hour"),
            (self.t + self.oneday + self.onehour, 1, "1\xa0day"),
            (self.t + self.oneweek + self.oneday, 1, "1\xa0week"),
            (self.t + self.onemonth + self.oneweek, 1, "1\xa0month"),
            (self.t + self.oneyear + self.onemonth, 1, "1\xa0year"),
            (self.t + self.oneyear + self.oneweek + self.oneday, 3, "1\xa0year"),
        ]
        for value, depth, expected in tests:
            with self.subTest():
                self.assertEqual(timesince(self.t, value, depth=depth), expected)
                self.assertEqual(timeuntil(value, self.t, depth=depth), expected)

    def test_months_edge(self):
        t = datetime.datetime(2022, 1, 1)
        tests = [
            (datetime.datetime(2022, 1, 31), "4\xa0weeks, 2\xa0days"),
            (datetime.datetime(2022, 2, 1), "1\xa0month"),
            (datetime.datetime(2022, 2, 28), "1\xa0month, 3\xa0weeks"),
            (datetime.datetime(2022, 3, 1), "2\xa0months"),
            (datetime.datetime(2022, 3, 31), "2\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 4, 1), "3\xa0months"),
            (datetime.datetime(2022, 4, 30), "3\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 5, 1), "4\xa0months"),
            (datetime.datetime(2022, 5, 31), "4\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 6, 1), "5\xa0months"),
            (datetime.datetime(2022, 6, 30), "5\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 7, 1), "6\xa0months"),
            (datetime.datetime(2022, 7, 31), "6\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 8, 1), "7\xa0months"),
            (datetime.datetime(2022, 8, 31), "7\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 9, 1), "8\xa0months"),
            (datetime.datetime(2022, 9, 30), "8\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 10, 1), "9\xa0months"),
            (datetime.datetime(2022, 10, 31), "9\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 11, 1), "10\xa0months"),
            (datetime.datetime(2022, 11, 30), "10\xa0months, 4\xa0weeks"),
            (datetime.datetime(2022, 12, 1), "11\xa0months"),
            (datetime.datetime(2022, 12, 31), "11\xa0months, 4\xa0weeks"),
        ]
        for value, expected in tests:
            with self.subTest():
                self.assertEqual(timesince(t, value), expected)

    def test_depth_invalid(self):
        msg = "depth must be greater than 0."
        with self.assertRaisesMessage(ValueError, msg):
            timesince(self.t, self.t, depth=0)

    @requires_tz_support
    def test_less_than_a_day_with_zoneinfo(self):
        now_with_zoneinfo = timezone.now().astimezone(
            zoneinfo.ZoneInfo(key="Asia/Kathmandu")  
        )
        tests = [
            (now_with_zoneinfo, "0\xa0minutes"),
            (now_with_zoneinfo - self.onemicrosecond, "0\xa0minutes"),
            (now_with_zoneinfo - self.onesecond, "0\xa0minutes"),
            (now_with_zoneinfo - self.oneminute, "1\xa0minute"),
            (now_with_zoneinfo - self.onehour, "1\xa0hour"),
        ]
        for value, expected in tests:
            with self.subTest(value):
                self.assertEqual(timesince(value), expected)

    @requires_tz_support
    def test_less_than_a_day_cross_day_with_zoneinfo(self):
        now_with_zoneinfo = timezone.make_aware(
            datetime.datetime(2023, 4, 14, 1, 30, 30),
            zoneinfo.ZoneInfo(key="Asia/Kathmandu"),  
        )
        now_utc = now_with_zoneinfo.astimezone(datetime.timezone.utc)
        tests = [
            (now_with_zoneinfo, "0\xa0minutes"),
            (now_with_zoneinfo - self.onemicrosecond, "0\xa0minutes"),
            (now_with_zoneinfo - self.onesecond, "0\xa0minutes"),
            (now_with_zoneinfo - self.oneminute, "1\xa0minute"),
            (now_with_zoneinfo - self.onehour, "1\xa0hour"),
        ]
        for value, expected in tests:
            with self.subTest(value):
                self.assertEqual(timesince(value, now_utc), expected)


@requires_tz_support
@override_settings(USE_TZ=True)
class TZAwareTimesinceTests(TimesinceTests):
    def setUp(self):
        super().setUp()
        self.t = timezone.make_aware(self.t, timezone.get_default_timezone())