import random from datetime import datetime, timedelta import pytest import pytz import maya from maya.compat import cmp Los_Angeles = pytz.timezone('America/Los_Angeles') New_York = pytz.timezone('America/New_York') Melbourne = pytz.timezone('Australia/Melbourne') def test_interval_requires_2_of_start_end_duration(): start = maya.now() end = start.add(hours=1) with pytest.raises(ValueError): maya.MayaInterval(start=start) with pytest.raises(ValueError): maya.MayaInterval(end=end) with pytest.raises(ValueError): maya.MayaInterval(duration=60) with pytest.raises(ValueError): maya.MayaInterval(start=start, end=end, duration=60) maya.MayaInterval(start=start, end=end) maya.MayaInterval(start=start, duration=60) maya.MayaInterval(end=end, duration=60) def test_interval_requires_end_time_after_or_on_start_time(): with pytest.raises(ValueError): maya.MayaInterval(start=maya.now(), duration=0) maya.MayaInterval(start=maya.now(), duration=-1) def test_interval_init_start_end(): start = maya.now() end = start.add(hours=1) interval = maya.MayaInterval(start=start, end=end) assert interval.start == start assert interval.end == end def test_interval_init_start_duration(): start = maya.now() duration = 1 interval = maya.MayaInterval(start=start, duration=duration) assert interval.start == start assert interval.end == start.add(seconds=duration) def test_interval_init_end_duration(): end = maya.now() duration = 1 interval = maya.MayaInterval(end=end, duration=duration) assert interval.end == end assert interval.start == end.subtract(seconds=duration) @pytest.mark.parametrize( 'start_doy1,end_doy1,start_doy2,end_doy2,intersection_doys', ( (0, 2, 1, 3, (1, 2)), (0, 2, 3, 4, None), (0, 2, 2, 3, None), (0, 1, 0, 1, (0, 1)), (1, 1, 1, 3, (1, 1)), (1, 1, 1, 1, (1, 1)), (1, 1, 2, 3, None), (2, 2, 1, 3, (2, 2)), (1, 3, 1, 1, (1, 1)), (2, 3, 1, 1, None), (1, 3, 2, 2, (2, 2)), ), ids=( 'overlapping', 'non-overlapping', 'adjacent', 'equal', 'instant overlapping start only', 'instant equal', 'instant disjoint', 'instant overlapping', 'instant overlapping start only (left)', 'instant disjoint (left)', 'instant overlapping (left)', ), ) def test_interval_intersection( start_doy1, end_doy1, start_doy2, end_doy2, intersection_doys ): base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) interval1 = maya.MayaInterval( base.add(days=start_doy1), base.add(days=end_doy1) ) interval2 = maya.MayaInterval( base.add(days=start_doy2), base.add(days=end_doy2) ) if intersection_doys: start_doy_intersection, end_doy_intersection = intersection_doys assert interval1 & interval2 == maya.MayaInterval( base.add(days=start_doy_intersection), base.add(days=end_doy_intersection), ) else: assert (interval1 & interval2) is None # check invalid argument with pytest.raises(TypeError): interval1 & 'invalid type' def test_interval_intersects(): base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) interval = maya.MayaInterval(base, base.add(days=1)) assert interval.intersects(interval) assert not interval.intersects( maya.MayaInterval(base.add(days=2), base.add(days=3)) ) # check invalid argument with pytest.raises(TypeError): interval.intersects('invalid type') def test_and_operator(): base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) interval1 = maya.MayaInterval(base, base.add(days=2)) interval2 = maya.MayaInterval(base.add(days=1), base.add(days=3)) assert ( interval1 & interval2 == interval2 & interval1 == interval1.intersection(interval2) ) # check invalid argument with pytest.raises(TypeError): interval1.intersection('invalid type') def test_interval_eq_operator(): start = maya.now() end = start.add(hours=1) interval = maya.MayaInterval(start=start, end=end) assert interval == maya.MayaInterval(start=start, end=end) assert interval != maya.MayaInterval(start=start, end=end.add(days=1)) # check invalid argument with pytest.raises(TypeError): interval == 'invalid type' with pytest.raises(TypeError): interval != 'invalid type' def test_interval_timedelta(): start = maya.now() delta = timedelta(hours=1) interval = maya.MayaInterval(start=start, duration=delta) assert interval.timedelta == delta def test_interval_duration(): start = maya.now() delta = timedelta(hours=1) interval = maya.MayaInterval(start=start, duration=delta) assert interval.duration == delta.total_seconds() @pytest.mark.parametrize( 'start_doy1,end_doy1,start_doy2,end_doy2,expected', ( (0, 2, 1, 3, False), (0, 2, 3, 4, False), (0, 2, 2, 3, False), (0, 1, 0, 1, True), (0, 3, 1, 2, True), ), ids=('overlapping', 'non-overlapping', 'adjacent', 'equal', 'subset'), ) def test_interval_contains( start_doy1, end_doy1, start_doy2, end_doy2, expected ): base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) interval1 = maya.MayaInterval( base.add(days=start_doy1), base.add(days=end_doy1) ) interval2 = maya.MayaInterval( base.add(days=start_doy2), base.add(days=end_doy2) ) assert interval1.contains(interval2) is expected assert (interval2 in interval1) is expected # check invalid argument with pytest.raises(TypeError): interval1.contains('invalid type') @pytest.mark.parametrize( 'start_doy,end_doy,dt_doy,expected', ( (2, 4, 1, False), (2, 4, 2, True), (2, 4, 3, True), (2, 4, 4, False), (2, 4, 5, False), ), ids=('before-start', 'on-start', 'during', 'on-end', 'after-end'), ) def test_interval_in_operator_maya_dt(start_doy, end_doy, dt_doy, expected): base = maya.MayaDT.from_datetime(datetime(2016, 1, 1)) interval = maya.MayaInterval( start=base.add(days=start_doy), end=base.add(days=end_doy) ) dt = base.add(days=dt_doy) assert (dt in interval) is expected # check invalid argument with pytest.raises(TypeError): 'invalid type' in interval def test_interval_hash(): start = maya.now() end = start.add(hours=1) interval = maya.MayaInterval(start=start, end=end) assert hash(interval) == hash(maya.MayaInterval(start=start, end=end)) assert hash(interval) != hash( maya.MayaInterval(start=start, end=end.add(days=1)) ) def test_interval_iter(): start = maya.now() end = start.add(days=1) assert tuple(maya.MayaInterval(start=start, end=end)) == (start, end) @pytest.mark.parametrize( 'start1,end1,start2,end2,expected', [(1, 2, 1, 2, 0), (1, 3, 2, 4, -1), (2, 4, 1, 3, 1), (1, 2, 1, 3, -1)], ids=( 'equal', 'less-than', 'greater-than', 'use-end-time-if-start-time-identical', ), ) def test_interval_cmp(start1, end1, start2, end2, expected): base = maya.now() interval1 = maya.MayaInterval( start=base.add(days=start1), end=base.add(days=end1) ) interval2 = maya.MayaInterval( start=base.add(days=start2), end=base.add(days=end2) ) assert cmp(interval1, interval2) == expected # check invalid argument with pytest.raises(TypeError): cmp(interval1, 'invalid type') @pytest.mark.parametrize( 'start1,end1,start2,end2,expected', [ (1, 2, 2, 3, [(1, 3)]), (1, 3, 2, 4, [(1, 4)]), (1, 2, 3, 4, [(1, 2), (3, 4)]), (1, 5, 2, 3, [(1, 5)]), ], ids=('adjacent', 'overlapping', 'non-overlapping', 'contains'), ) def test_interval_combine(start1, end1, start2, end2, expected): base = maya.now() interval1 = maya.MayaInterval( start=base.add(days=start1), end=base.add(days=end1) ) interval2 = maya.MayaInterval( start=base.add(days=start2), end=base.add(days=end2) ) expected_intervals = [ maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) for start, end in expected ] assert interval1.combine(interval2) == expected_intervals assert interval2.combine(interval1) == expected_intervals # check invalid argument with pytest.raises(TypeError): interval2.combine('invalid type') @pytest.mark.parametrize( 'start1,end1,start2,end2,expected', [ (1, 2, 3, 4, [(1, 2)]), (1, 2, 2, 4, [(1, 2)]), (2, 3, 1, 4, []), (1, 4, 2, 3, [(1, 2), (3, 4)]), (1, 4, 0, 2, [(2, 4)]), (1, 4, 3, 5, [(1, 3)]), (1, 4, 1, 2, [(2, 4)]), (1, 4, 3, 4, [(1, 3)]), ], ids=( 'non-overlapping', 'adjacent', 'contains', 'splits', 'overlaps-left', 'overlaps-right', 'overlaps-left-identical-start', 'overlaps-right-identical-end', ), ) def test_interval_subtract(start1, end1, start2, end2, expected): base = maya.now() interval1 = maya.MayaInterval( start=base.add(days=start1), end=base.add(days=end1) ) interval2 = maya.MayaInterval( start=base.add(days=start2), end=base.add(days=end2) ) expected_intervals = [ maya.MayaInterval(start=base.add(days=start), end=base.add(days=end)) for start, end in expected ] assert interval1.subtract(interval2) == expected_intervals # check invalid argument with pytest.raises(TypeError): interval1.subtract('invalid type') @pytest.mark.parametrize( 'start1,end1,start2,end2,expected', [ (1, 2, 2, 3, True), (2, 3, 1, 2, True), (1, 3, 2, 3, False), (2, 3, 4, 5, False), ], ids=('adjacent-right', 'adjacent-left', 'overlapping', 'non-overlapping'), ) def test_interval_is_adjacent(start1, end1, start2, end2, expected): base = maya.now() interval1 = maya.MayaInterval( start=base.add(days=start1), end=base.add(days=end1) ) interval2 = maya.MayaInterval( start=base.add(days=start2), end=base.add(days=end2) ) assert interval1.is_adjacent(interval2) == expected # check invalid argument with pytest.raises(TypeError): interval1.is_adjacent('invalid type') @pytest.mark.parametrize( 'start,end,delta,include_remainder,expected', [ (0, 10, 5, False, [(0, 5), (5, 10)]), (0, 10, 5, True, [(0, 5), (5, 10)]), (0, 10, 3, False, [(0, 3), (3, 6), (6, 9)]), (0, 10, 3, True, [(0, 3), (3, 6), (6, 9), (9, 10)]), (0, 2, 5, False, []), (0, 2, 5, True, [(0, 2)]), ], ids=( 'even-split', 'even-split-include-partial', 'uneven-split-do-not-include-partial', 'uneven-split-include-partial', 'delta-larger-than-timepsan-do-not-include-partial', 'delta-larger-than-timepsan-include-partial', ), ) def test_interval_split(start, end, delta, include_remainder, expected): base = maya.now() interval = maya.MayaInterval( start=base.add(days=start), end=base.add(days=end) ) delta = timedelta(days=delta) expected_intervals = [ maya.MayaInterval(start=base.add(days=s), end=base.add(days=e)) for s, e in expected ] assert expected_intervals == list( interval.split(delta, include_remainder=include_remainder) ) def test_interval_split_non_positive_delta(): start = maya.now() end = start.add(days=1) interval = maya.MayaInterval(start=start, end=end) with pytest.raises(ValueError): list(interval.split(timedelta(seconds=0))) with pytest.raises(ValueError): list(interval.split(timedelta(seconds=-10))) @pytest.mark.parametrize( 'start,end,minutes,timezone,snap_out,expected_start,expected_end', [ ((5, 12), (8, 48), 30, None, False, (5, 30), (8, 30)), ((5, 12), (8, 48), 30, None, True, (5, 0), (9, 0)), ((5, 15), (9, 0), 15, None, False, (5, 15), (9, 0)), ((5, 15), (9, 0), 15, None, True, (5, 15), (9, 0)), ((6, 50), (9, 15), 60, 'America/New_York', False, (7, 0), (9, 0)), ((6, 50), (9, 15), 60, 'America/New_York', True, (6, 0), (10, 0)), ((6, 20), (6, 50), 60, None, False, (6, 0), (6, 0)), ((6, 20), (6, 50), 60, None, True, (6, 0), (7, 0)), ((6, 20), (6, 50), 60, 'America/Chicago', False, (6, 0), (6, 0)), ((6, 20), (6, 50), 60, 'America/Chicago', True, (6, 0), (7, 0)), ], ids=( 'normal', 'normal-snap_out', 'already-quantized', 'already-quantized-snap_out', 'with-timezone', 'with-timezone-snap_out', 'too-small', 'too-small-snap_out', 'too-small-with-timezone', 'too-small-with-timezone-snap_out', ), ) def test_quantize( start, end, minutes, timezone, snap_out, expected_start, expected_end ): base = maya.MayaDT.from_datetime(datetime(2017, 1, 1)) interval = maya.MayaInterval( start=base.add(hours=start[0], minutes=start[1]), end=base.add(hours=end[0], minutes=end[1]), ) kwargs = {'timezone': timezone} if timezone is not None else {} quantized_interval = interval.quantize( timedelta(minutes=minutes), snap_out=snap_out, **kwargs ) assert quantized_interval == maya.MayaInterval( start=base.add(hours=expected_start[0], minutes=expected_start[1]), end=base.add(hours=expected_end[0], minutes=expected_end[1]), ) def test_quantize_invalid_delta(): start = maya.now() end = start.add(days=1) interval = maya.MayaInterval(start=start, end=end) with pytest.raises(ValueError): interval.quantize(timedelta(minutes=0)) with pytest.raises(ValueError): interval.quantize(timedelta(minutes=-1)) def test_interval_flatten_non_overlapping(): step = 2 max_hour = 20 base = maya.now() intervals = [ maya.MayaInterval( start=base.add(hours=hour), duration=timedelta(hours=step - 1) ) for hour in range(0, max_hour, step) ] random.shuffle(intervals) assert maya.MayaInterval.flatten(intervals) == sorted(intervals) def test_interval_flatten_adjacent(): step = 2 max_hour = 20 base = maya.when('jan/1/2011') intervals = [ maya.MayaInterval( start=base.add(hours=hour), duration=timedelta(hours=step) ) for hour in range(0, max_hour, step) ] random.shuffle(intervals) assert maya.MayaInterval.flatten(intervals) == [ maya.MayaInterval(start=base, duration=timedelta(hours=max_hour)) ] def test_interval_flatten_intersecting(): step = 2 max_hour = 20 base = maya.now() intervals = [ maya.MayaInterval( start=base.add(hours=hour), duration=timedelta(hours=step, minutes=30), ) for hour in range(0, max_hour, step) ] random.shuffle(intervals) assert maya.MayaInterval.flatten(intervals) == [ maya.MayaInterval( start=base, duration=timedelta(hours=max_hour, minutes=30) ) ] def test_interval_flatten_containing(): step = 2 max_hour = 20 base = maya.now() containing_interval = maya.MayaInterval( start=base, end=base.add(hours=max_hour + step) ) intervals = [ maya.MayaInterval( start=base.add(hours=hour), duration=timedelta(hours=step - 1) ) for hour in range(2, max_hour, step) ] intervals.append(containing_interval) random.shuffle(intervals) assert maya.MayaInterval.flatten(intervals) == [containing_interval] def test_interval_from_datetime(): start = maya.now() duration = timedelta(hours=1) end = start + duration interval = maya.MayaInterval.from_datetime( start_dt=start.datetime(naive=False), end_dt=end.datetime(naive=False) ) assert interval.start == start assert interval.end == end interval2 = maya.MayaInterval.from_datetime( start_dt=start.datetime(naive=False), duration=duration ) assert interval2.start == start assert interval2.end == end interval3 = maya.MayaInterval.from_datetime( end_dt=end.datetime(naive=False), duration=duration ) assert interval3.start == start assert interval3.end == end def test_interval_iso8601(): start = maya.when('11-17-11 08:09:10') interval = maya.MayaInterval(start=start, duration=1) assert interval.iso8601() == '2011-11-17T08:09:10Z/2011-11-17T08:09:11Z' def test_interval_from_iso8601(): interval = maya.MayaInterval.from_iso8601( "2018-03-18T14:27:18Z/2018-04-01T04:15:27Z" ) s = maya.when("2018-03-18T14:27:18Z") e = maya.when("2018-04-01T04:15:27Z") assert interval.start == s assert interval.end == e def test_interval_from_iso8601_duration(): interval = maya.MayaInterval.from_iso8601( "2018-03-18T14:27:18Z/P13DT13H48M9S" ) s = maya.when("2018-03-18T14:27:18Z") e = maya.when("2018-04-01T04:15:27Z") assert interval.start == s assert interval.end == e interval = maya.MayaInterval.from_iso8601( "2018-03-05T14:27:18Z/P2W" ) s = maya.when("2018-03-05T14:27:18Z") e = maya.when("2018-03-19T14:27:18Z") assert interval.start == s assert interval.end == e