Cycle tag - last cycle returns same cycle
As you already found out, the loop.index counter increments for each iteration of the loop, not only if you use or print it within your conditional. The best solution to work against it is to get rid of that conditional.

In this case it is in fact possible to catch only those entries you are after, making the conditional redundent

{% for entry in craft.entries.section('gigs').datum('&lt; ' ~ present) %}
    &lt;h2 class="{{ cycle(['odd','even'], loop.index0) }}"&gt;{{ entry.title }&lt;/h2&gt;
{% endfor %}


if it wasn't, you could have done your own counter that increments within the conditional.

{% set index = 0 %}
{% for entry in craft.entries.section('gigs') %}
    {% if entry.datum &lt; present %}
        {% set index = index + 1 %}
        &lt;h2 class="{{ cycle(['odd','even'], index) }}"&gt;{{ entry.title }&lt;/h2&gt;
    {% endif %}
{% endfor %}