text: from datetime import date, datetime, timedelta
  import unittest
  
  import pytz
  
  from e_time import (
      parse_repeat_phrase, parse_single_event, parse_time_range,
  )
  from e_time.tokens_and_syntax import (
      parse, AmPm, Comma, Dash, Day, Days, Midnight, Month, Noon, Number, String,
  )
  from e_time.parser import _guess_year
  
  TIME_ZONE = 'US/Eastern'
  PYTZ_TIME_ZONE = pytz.timezone(TIME_ZONE)
  
  
  class TestSingleEvent(unittest.TestCase):
  
      def setUp(self):
          self.now = PYTZ_TIME_ZONE.localize(datetime.now())
  
      def test(self):
          starts_at, ends_at = parse_single_event('january 13 9-11pm')
          # It may decide that January 13 is next year.
          expected_year = _guess_year(1, 13, PYTZ_TIME_ZONE, self.now)
          self.assertIn(
              expected_year,
              [self.now.year, self.now.year + 1]
          )
          self.assertEqual(
              datetime(expected_year, 1, 13, 21, 0),
              starts_at
          )
          self.assertEqual(
              datetime(expected_year, 1, 13, 23, 0),
              ends_at
          )
          not_now = self.now.replace(year=2015, month=1, day=1)
          starts_at, ends_at = parse_single_event(
              'january 13 9-11pm', now=not_now, local_tz=PYTZ_TIME_ZONE
          )
          self.assertEqual(
              PYTZ_TIME_ZONE.localize(datetime(not_now.year, 1, 13, 21, 0)),
              starts_at
          )
          self.assertEqual(
              PYTZ_TIME_ZONE.localize(datetime(not_now.year, 1, 13, 23, 0)),
              ends_at
          )
  
      def test_no_stop_time(self):
          # It may decide that January 13 is next year.
          expected_year = _guess_year(1, 13, PYTZ_TIME_ZONE, self.now)
          self.assertIn(
              expected_year,
              [self.now.year, self.now.year + 1]
          )
          starts_at, ends_at = parse_single_event('january 13 9:45pm')
          self.assertEqual(
              datetime(expected_year, 1, 13, 21, 45),
              starts_at
          )
          self.assertEqual(
              None,
              ends_at
          )
  
      def test_early_next_year(self):
          month = 12
          day = 31
          year = 2017
          starts_at, ends_at = parse_single_event(
              'january 1 9-11:30pm',
              now=datetime(year, month, day, 13, 30)
          )
          self.assertEqual(
              datetime(year + 1, 1, 1, 21, 0),
              starts_at
          )
          self.assertEqual(
              datetime(year + 1, 1, 1, 23, 30),
              ends_at
          )
  
      def test_late_last_year(self):
          month = 1
          day = 1
          year = 2018
          starts_at, ends_at = parse_single_event(
              'december 31 9-11:30pm',
              now=datetime(year, month, day, 13, 30)
          )
          self.assertEqual(
              datetime(year - 1, 12, 31, 21, 0),
              starts_at
          )
          self.assertEqual(
              datetime(year - 1, 12, 31, 23, 30),
              ends_at
          )
  
      def test_range_with_year(self):
          month = 1
          day = 1
          year = 2018
          # note that one has '9pm' instead of just '9', and one has comma before year
          for s in ('december 31, 2016 9pm-11:30pm', 'december 31 2016 9-11:30pm'):
              starts_at, ends_at = parse_single_event(
                  s,
                  now=datetime(year, month, day, 13, 30)
              )
              self.assertEqual(
                  datetime(2016, 12, 31, 21, 0),
                  starts_at
              )
              self.assertEqual(
                  datetime(2016, 12, 31, 23, 30),
                  ends_at
              )
  
      def test_start_time_with_year(self):
          month = 1
          day = 1
          year = 2018
          for s in ('december 31, 2016 9pm', 'december 31 2016 9pm'):
              starts_at, ends_at = parse_single_event(
                  s,
                  now=datetime(year, month, day, 13, 30)
              )
              self.assertEqual(
                  datetime(2016, 12, 31, 21, 0),
                  starts_at
              )
              self.assertEqual(
                  None,
                  ends_at
              )
  
      def test_start_end_without_year(self):
          s = 'June 25   5:00PM — 8:00PM'
          starts_at, ends_at = parse_single_event(
              s,
              now=datetime(2019, 5, 1, 13, 30)
          )
          self.assertEqual(datetime(2019, 6, 25, 17, 0), starts_at)
          self.assertEqual(datetime(2019, 6, 25, 20, 0), ends_at)
  
      def test_bad_string(self):
          with self.assertRaises(ValueError):
              parse_single_event('1 december 31 2017 9-11:30pm')
  
  
  class TestTimeRange(unittest.TestCase):
  
      def test_em(self):
          now = PYTZ_TIME_ZONE.localize(datetime.now())
          month = 12
          day = 31
          year = now.year
  
          t_4am = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 4, 0))
          t_12pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 12))
          t_1230pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 12, 30))
          t_2pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 14))
          t_7pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 19))
          t_8pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 20))
          t_830pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 20, 30))
          t_9pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 21))
          t_930pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 21, 30))
          t_11pm = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 23))
          t_12am = PYTZ_TIME_ZONE.localize(datetime(year, month, day, 23) + timedelta(hours=1))
          test_cases = (
              ('noon-2pm', t_12pm, t_2pm),
              ('9pm-midnight', t_9pm, t_12am),
              ('4am-12:30pm', t_4am, t_1230pm),
              ('4a-12:30pm', t_4am, t_1230pm),
              ('12:30p-2pm', t_1230pm, t_2pm),
              ('12:30pm-2pm', t_1230pm, t_2pm),
              ('9pm-12am', t_9pm, t_12am),
              ('9pm', t_9pm, None),
              ('7pm-8:30pm', t_7pm, t_830pm),
              ('7-9 p.m.', t_7pm, t_9pm),
              ('7pm-8:30', t_7pm, t_830pm),
              ('8:00 pm - 11:00 pm', t_8pm, t_11pm),
              ('7pm-11pm', t_7pm, t_11pm),
              ('7p-11p', t_7pm, t_11pm),
              ('8pm – 11pm', t_8pm, t_11pm),
              ('8:30 – 9:30 PM', t_830pm, t_930pm),
              ('8pm-12am', t_8pm, t_12am),
              ('9 PM – 12 AM', t_9pm, t_12am),
              ('9p-12a', t_9pm, t_12am),
          )
          for time_range, expected_starts_at, expected_ends_at in test_cases:
              actual_starts_at, actual_ends_at = \
                  parse_time_range(date(year, month, day), time_range, PYTZ_TIME_ZONE)
              self.assertEqual(
                  expected_starts_at, actual_starts_at,
                  'Determining starts_at failed for %s' % time_range
              )
              self.assertEqual(
                  expected_ends_at, actual_ends_at,
                  'Determining ends_at failed for %s' % time_range
              )
  
  
  class TestTokenizing(unittest.TestCase):
  
      def test(self):
          samples = (
              ('Dec 29 7-9pm', (Month, Number, Number, Dash, Number, AmPm)),
              ('dec 31 7pm-9', (Month, Number, Number, AmPm, Dash, Number)),
              ('Every Monday 7-9pm', (String, Day, Number, Dash, Number, AmPm)),
              ('Mondays 8:30pm', (Days, Number, AmPm)),
              ('january 13 9-11pm', (Month, Number, Number, Dash, Number, AmPm)),
              ('january 13, 2018 9-11pm', (Month, Number, Comma, Number, Number, Dash, Number, AmPm)),
              ('9pm', (Number, AmPm)),
              ('9p', (Number, AmPm)),
              ('9 PM - 12 AM', (Number, AmPm, Dash, Number, AmPm)),
              ('7pm-8:30', (Number, AmPm, Dash, Number)),
              ('9pm-Midnight', (Number, AmPm, Dash, Midnight)),
              ('noon-4pm', (Noon, Dash, Number, AmPm)),
          )
  
          for sample, syntax in samples:
              expected_syntax = list(syntax)
              actual = parse(sample)
              actual_syntax = [x for x, _ in actual]
              self.assertEqual(actual_syntax, expected_syntax, 'Checking "%s"' % sample)
  
  
  class TestRepeatPhrase(unittest.TestCase):
  
      def test_with_just_start_time(self):
          now = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 5, 19))
          phrase = '1st and 3rd Wednesdays 8:30pm'
          rv = parse_repeat_phrase(
              phrase, timedelta(days=31), local_tz=PYTZ_TIME_ZONE, now=now
          )
          occurrence_1 = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 7, 20, 30))
          occurrence_2 = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 21, 20, 30))
          occurrence_3 = PYTZ_TIME_ZONE.localize(datetime(2018, 4, 4, 20, 30))
          self.assertEqual(
              [
                  (occurrence_1, None),
                  (occurrence_2, None),
                  (occurrence_3, None),
              ],
              list(rv)
          )
  
      def test_with_time_range_1(self):
          now = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 1, 19))
          phrase = '1st Fridays 8:30pm-12:30am'
          rv = parse_repeat_phrase(
              phrase, timedelta(days=40), local_tz=PYTZ_TIME_ZONE, now=now
          )
          occurrence_1 = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 2, 20, 30))
          occurrence_2 = PYTZ_TIME_ZONE.localize(datetime(2018, 4, 6, 20, 30))
          self.assertEqual(
              [
                  (occurrence_1, occurrence_1 + timedelta(hours=4)),
                  (occurrence_2, occurrence_2 + timedelta(hours=4)),
              ],
              list(rv)
          )
  
      def test_with_time_range_2(self):
          now = PYTZ_TIME_ZONE.localize(datetime(2018, 2, 22, 12))
          phrase = 'Every other Thursday 8-11pm'
          rv = parse_repeat_phrase(
              phrase, timedelta(days=57), local_tz=PYTZ_TIME_ZONE, now=now
          )
          occurrence_1 = PYTZ_TIME_ZONE.localize(datetime(2018, 2, 22, 20, 0))
          occurrence_2 = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 8, 20, 0))
          occurrence_3 = PYTZ_TIME_ZONE.localize(datetime(2018, 3, 22, 20, 0))
          occurrence_4 = PYTZ_TIME_ZONE.localize(datetime(2018, 4, 5, 20, 0))
          occurrence_5 = PYTZ_TIME_ZONE.localize(datetime(2018, 4, 19, 20, 0))
          self.assertEqual(
              [
                  (occurrence_1, occurrence_1 + timedelta(hours=3)),
                  (occurrence_2, occurrence_2 + timedelta(hours=3)),
                  (occurrence_3, occurrence_3 + timedelta(hours=3)),
                  (occurrence_4, occurrence_4 + timedelta(hours=3)),
                  (occurrence_5, occurrence_5 + timedelta(hours=3)),
              ],
              list(rv)
          )
  
      def test_every_occurrence(self):
          now = PYTZ_TIME_ZONE.localize(datetime(2018, 2, 22, 12))
          phrase = 'Thursdays 8pm-12am'
          rv = parse_repeat_phrase(
              phrase, timedelta(days=57), local_tz=PYTZ_TIME_ZONE, now=now
          )
          start_times = [
              PYTZ_TIME_ZONE.localize(datetime(2018, 2, 22, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 3, 1, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 3, 8, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 3, 15, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 3, 22, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 3, 29, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 4, 5, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 4, 12, 20, 0)),
              PYTZ_TIME_ZONE.localize(datetime(2018, 4, 19, 20, 0))
          ]
          start_stop_times = [
              (start, start + timedelta(hours=4))
              for start in start_times
          ]
          self.assertEqual(
              start_stop_times,
              list(rv)
          )
  