After all these years, I still struggle whenever I have to play with dates in WordPress. Between wp_date(), date_i18n(), get_the_date(), and of course the different date options of PHP, I always find it hard to start. Sprinkle a bit of timezone on top of that, and I find myself having to check the docs every time!
In today’s example, I wanted to generate a localized date and time when a post was published. Something like June 11, 2025, at 8:24 am in English or le 11 juin 2025 à 08h24. Seems simple enough.
Timezones
Let’s start with getting the timezone out of the way. My site is set to the timezone of Paris (UTC +2 right now). Luckily, in WordPress the timezone info is easily accessible via wp_timezone() or wp_timezone_string():
wp_timezone_string()gives meEurope/Paris.wp_timezone()gives me aDateTimeZoneobject with the timezone:
DateTimeZone {#8386
timezone: Europe/Paris (+02:00),
}
I think I can work with that!
Using DateTimeImmutable (get_post_datetime) and wp_date
I would need to get the post’s full publishing date and time. I could then turn it into a DateTimeImmutable object that would take that timezone into account. Luckily, WordPress gives us post_date in the WP_Post object: 2025-06-11 08:24:00
My first idea was to turn this into a DateTimeImmutable object, passing it wp_timezone():
$post = get_post( $post_id );
$publishing_date = new \DateTimeImmutable(
$post->post_date,
wp_timezone()
);
DateTimeImmutable @1749623040 {#8416
date: 2025-06-11 08:24:00.0 Europe/Paris (+02:00),
}
But it turns out WordPress can do this for us! get_post_datetime() to the rescue:
$post = get_post( $post_id );
$post_datetime = get_post_datetime( $post_id );
DateTimeImmutable @1749623040 {#8399
date: 2025-06-11 08:24:00.0 Europe/Paris (+02:00),
}
At this point, all what’s left is to format and localize that date, so the result can be used regardless of the site language and date / time format. This is where wp_date() comes in. We’ll provide it with a localized format, and the Unix timestamp from our DateTimeImmutable object.
Let’s build our localized format first. This is the part that I find clunky, and prone to issues because of the translatable string:
$datetime_format = sprintf(
/* Translators: %1$s is a formatted date, e.g. June 11, 2025. %2$s is a formatted time, e.g. 8:24 am. All other words/letters need to be escaped. */
__( '%1$s \a\t %2$s', 'my-textdomain' ),
get_option( 'date_format' ),
get_option( 'time_format' )
);
On a French site, this may give us \l\e j F Y \à H:i.
Feed that to wp_date(), together with the Unix timestamp from our DateTimeImmutable object:
$formatted_publishing_date = wp_date(
$datetime_format,
get_post_datetime( $post_id )->getTimestamp()
);
And you end up with le 11 juin 2025 à 08:24.
This works, but it all seems unnecessarily complicated. 🙂