If you don’t want to use a scheduler (ex: Action Scheduler) or a cron job, and you only want to update once per year, then an option that tracks the next update time is probably the simplest approach (untested):
add_action( 'init', static function () {
$option_name="cpt_ages_next_update";
$option = absint( get_option( $option_name ) );
if ( $option > time() ) {
return;
}
$query = new WP_Query( array(
'post_type' => 'cpt',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true,
) );
if ( ! $query->have_posts() ) {
return;
}
foreach ( $query->posts as $post_id ) {
$year = absint( get_field( 'birth_year', $post_id ) );
$age = absint( date( 'Y' ) ) - $year;
update_field( 'age', $age );
}
update_option( $option_name, time() + YEAR_IN_SECONDS );
} );
I would recommend using a plugin like Action Scheduler though, especially if you the number of posts is very high.