docstr: Contains a date range to the trading availability of the specified
  markets.
  
  Parameters
  ----------
  start: pd.Timestamp
  end: pd.Timestamp
  assets: list[TradingPair]
  data_frequency: str
  
  Returns
  -------
  pd.Timestamp, pd.Timestamp
docstr_tok: ["Contains","a","date","range","to","the","trading","availability","of","the","specified","markets",".","Parameters","--","--","--","--","--","start",":","pd.Timestamp","end",":","pd.Timestamp","assets",":","list","[","TradingPair","]","data_frequency",":","str","Returns","--","--","--","-","pd.Timestamp",",","pd.Timestamp"]
code: def get_adj_dates(self, start, end, assets, data_frequency):
      """
      Contains a date range to the trading availability of the specified
      markets.
  
      Parameters
      ----------
      start: pd.Timestamp
      end: pd.Timestamp
      assets: list[TradingPair]
      data_frequency: str
  
      Returns
      -------
      pd.Timestamp, pd.Timestamp
      """
      earliest_trade = None
      last_entry = None
      for asset in assets:
          if (
              earliest_trade is None
              or earliest_trade > asset.start_date
          ):
              if asset.start_date >= self.calendar.first_session:
                  earliest_trade = asset.start_date
  
              else:
                  earliest_trade = self.calendar.first_session
  
          end_asset = (
              asset.end_minute
              if data_frequency == "minute"
              else asset.end_daily
          )
          if end_asset is not None:
              if last_entry is None or end_asset > last_entry:
                  last_entry = end_asset
  
          else:
              end = None
              last_entry = None
  
      if start is None or (
          earliest_trade is not None and earliest_trade > start
      ):
          start = earliest_trade
  
      if last_entry is not None and (end is None or end > last_entry):
          end = (
              last_entry.replace(minute=59, hour=23)
              if data_frequency == "minute"
              else last_entry
          )
  
      if end is None or start is None or start > end:
          raise NoDataAvailableOnExchange(
              exchange=[asset.exchange for asset in assets],
              symbol=[asset.symbol for asset in assets],
              data_frequency=data_frequency,
          )
  
      return start, end
  
imports: import os
  import shutil
  from datetime import timedelta
  from functools import partial
  from itertools import chain
  from operator import is_not
  import numpy
  import pandas
  import pytz
  from catalyst import get_calendar
  from catalyst.assets._assets import TradingPair
  from catalyst.constants import DATE_TIME_FORMAT, AUTO_INGEST
  from catalyst.constants import LOG_LEVEL
  from catalyst.data.minute_bars import BcolzMinuteOverlappingData, BcolzMinuteBarMetadata
  from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, BcolzExchangeBarWriter
  from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, TempBundleNotFoundError, NoDataAvailableOnExchange, PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
  from catalyst.exchange.utils.bundle_utils import range_in_bundle, get_bcolz_chunk, get_df_from_arrays, get_assets
  from catalyst.exchange.utils.datetime_utils import get_start_dt, get_period_label, get_month_start_end, get_year_start_end
  from catalyst.exchange.utils.exchange_utils import get_exchange_folder, save_exchange_symbols, mixin_market_params, get_catalyst_symbol
  from catalyst.utils.cli import maybe_show_progress
  from catalyst.utils.paths import ensure_directory
  from logbook import Logger
  from pytz import UTC
  from six import itervalues
label_desc: Use image node_id 12 for calling the ExchangeBundle obj's underlying member method code with example usage: obj.get_adj_dates(start, end, assets, data_frequency) and returns: start,  end