• This is a well-documented "feature" of TRY...CATCH. From https://msdn.microsoft.com/en-us/library/ms175976.aspx:

    The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct:

    Compile errors, such as syntax errors, that prevent a batch from running.

    Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.

    These errors are returned to the level that ran the batch, stored procedure, or trigger.

    (emphasis mine)

    They actually then give an example of exactly this sort of thing, with a nonexistent table causing an error that bypasses the CATCH block.

    To test this, you could cause the error to occur at a level of execution lower than the TRY...CATCH construct, either by putting the UPDATEs in a stored procedure and calling the procedure from the TRY...CATCH construct, or as in my quick example based on yours, by running it using sp_executesql:

    DECLARE @RunDate DATETIME = DATEADD(hh,2,CAST(CAST(DATEADD(day,1,GETUTCDATE()) as DATE) as DATETIME))

    DECLARE @sql nvarchar(max)='BEGIN TRANSACTION

    UPDATE

    [TableA]

    SET

    ScheduleDate = @RunDateInner

    UPDATE

    [Table B]

    SET

    ScheduleDate = @RunDateInner

    COMMIT TRANSACTION'

    BEGIN TRY

    EXECUTE sp_executesql @statement=@sql,

    @params=N'@RundateInner datetime',

    @RunDateInner=@Rundate

    END TRY

    BEGIN CATCH

    If @@TRANCOUNT >0

    ROLLBACK tran

    END CATCH

    I hope this helps!