- Issue created by @Steven Snedker
Would this be better as a public static function on
\Drupal\Core\Datetime\DrupalDateTime
or similar than a global function, which this app's architecture is moving away from?- 🇩🇰Denmark Steven Snedker
Claude is pretty keen on the idea:
That's a great idea to simplify this common operation! Let me think through the architectural considerations:
Public static method vs. global function:
- Given Drupal's move away from global functions, a static method would indeed be more aligned with modern best practices
- It would maintain better encapsulation and namespace organization
- It follows object-oriented principles better than adding to the global namespace
- It makes the functionality more discoverable through IDE tooling
A static method could look something like:
public static function nowUtc(string $format = 'Y-m-d\TH:i:s'): string { return (new static('now', new \DateTimeZone('UTC')))->format($format); }
Then you could use it as:
$now = \Drupal\Core\Datetime\DrupalDateTime::nowUtc();
This gives you:
- A cleaner, more maintainable API
- The ability to easily extend with additional formats or timezones if needed
- Better consistency with modern PHP practices
- Clear indication that this is Drupal-specific datetime functionality
Would you like me to explore other potential approaches or elaborate on any aspects of this solution?
Trading
$now = (new \Drupal\Core\Datetime\DrupalDateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d\TH:i:s');
for
$now = \Drupal\Core\Datetime\DrupalDateTime::nowUtc();
is slightly better. You do not have to remember hyperspecific stuff like 'Y-m-d\TH:i:s'.Having it as a better service
# Create a new service class class DateTimeService { public function getNowUtc(string $format = 'Y-m-d\TH:i:s'): string { return (new DrupalDateTime('now', new \DateTimeZone('UTC')))->format($format); } }
would also trade very specific and human-unfriendly
$now = \Drupal::service('date.formatter')->format(time(), 'custom', 'Y-m-d\TH:i:s', 'UTC');
for better
$now = \Drupal::service('datetime.service')->getNowUtc();I'm not sure if which of the public static function or the service will play nicest with IDE tooling. Which one would require the least remembering aand typing?
My VS Code, filled to the brim with the right extensions → still makes me pine for simple, global functions.
$user = user_load(42);
is still way easier than
$user = \Drupal::entityTypeManager()->getStorage('user')->load(42); - 🇮🇳India nikhil_110
It's a great idea to implement a global helper function to simplify this common operation. If @cilefen and @steven.snedker agree with this approach, I can prepare a patch for the helper function. Let me know your thoughts!
Thanks - 🇩🇰Denmark Steven Snedker
Well, my preference is
- drupal_get_now() (global function, not the modern Drupal way, will probably not make it into core)
- \Drupal\Core\Datetime\DrupalDateTime::now() (You'll almost be able to remember this one)
- \Drupal::service('datetime.service')->now() (harder to remember)
Naming
now is short and easy to remember. It's not as distinct as getNowUTC, but your IDE would probably tell you that now returned the date+time as UTC.Make a patch @nikhil_110 and set the ball in motion!
- 🇮🇳India sourav_paul Kolkata
Then @cilefen what could be the best approach?
Please let us know, will work on that.. - 🇩🇰Denmark Steven Snedker
Looks very fine to me. Short, elegant and to the point.
Sadly I have no idea why we get "Merge request pipeline #339722 failed" at PHPUnit Functional 7/8 or how to get out of this regrettable situation.
- 🇺🇸United States nicxvan
If you click on it and search for fail you can find the failures.
It's layout builder which is a known common failure.
If you click get push access you can run that test group.
- 🇮🇳India sourav_paul Kolkata
It would be appreciating, if anyone could write the test coverage.
- 🇩🇰Denmark Steven Snedker
I hear you, brother @sourav_paul. So I asked Claude to do it:
I'll create a PHPUnit test for this static method from the DrupalDateTime class.
This test class provides comprehensive coverage for the now() method with several test cases:
testNowDefaultFormat(): Tests the default ISO 8601 format and verifies the timestamp is current
testNowCustomFormat(): Tests various custom format strings using a data provider
testNowTimezone(): Verifies that the method always uses UTC regardless of system timezoneKey features of the tests:
- Uses PHPUnit's data provider pattern to test multiple format strings
- Includes regex pattern validation for different date formats
- Handles timezone testing safely by restoring the original timezone
- Includes proper PHPDoc blocks and type hints
- Follows Drupal coding standards and testing conventions
You can run these tests using PHPUnit from your Drupal root directory:
./vendor/bin/phpunit core/tests/Drupal/Tests/Core/Datetime/DrupalDateTimeTest.phpAnd here's the test. I hope you can suss out where to put it:
<?php namespace Drupal\Tests\Core\Datetime; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Tests\UnitTestCase; /** * @coversDefaultClass \Drupal\Core\Datetime\DrupalDateTime * @group DateTime */ class DrupalDateTimeTest extends UnitTestCase { /** * Tests the default format of the now() method. * * @covers ::now */ public function testNowDefaultFormat(): void { $datetime = DrupalDateTime::now(); // Validate the format matches ISO 8601 $this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/', $datetime, 'Default format should match ISO 8601 format (Y-m-d\TH:i:s)' ); // Validate the timestamp is within 1 second of current time $now = time(); $test_time = strtotime($datetime); $this->assertLessThanOrEqual(1, abs($now - $test_time), 'Generated timestamp should be within 1 second of current time' ); } /** * Tests the now() method with custom formats. * * @covers ::now * @dataProvider provideTimeFormats */ public function testNowCustomFormat(string $format, string $pattern): void { $datetime = DrupalDateTime::now($format); $this->assertMatchesRegularExpression( $pattern, $datetime, sprintf('Format %s should match pattern %s', $format, $pattern) ); } /** * Data provider for testNowCustomFormat. * * @return array * Test cases with format strings and their corresponding regex patterns. */ public function provideTimeFormats(): array { return [ 'Year only' => [ 'Y', '/^\d{4}$/', ], 'Date only' => [ 'Y-m-d', '/^\d{4}-\d{2}-\d{2}$/', ], 'Time only' => [ 'H:i:s', '/^\d{2}:\d{2}:\d{2}$/', ], 'Custom format' => [ 'd/m/Y H:i', '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}$/', ], 'Unix timestamp' => [ 'U', '/^\d+$/', ], ]; } /** * Tests that the now() method uses UTC timezone. * * @covers ::now */ public function testNowTimezone(): void { // Store current timezone $current_timezone = date_default_timezone_get(); try { // Set system timezone to something other than UTC date_default_timezone_set('America/New_York'); // Get timestamp in both timezones $utc_time = strtotime(DrupalDateTime::now('Y-m-d H:i:s')); $ny_time = strtotime((new \DateTime('now', new \DateTimeZone('America/New_York')))->format('Y-m-d H:i:s')); // Calculate the offset (should be 4 or 5 hours depending on daylight savings) $offset = abs($ny_time - $utc_time); $this->assertTrue( $offset >= 14400 && $offset <= 18000, 'UTC time should differ from New York time by 4-5 hours' ); } finally { // Restore original timezone date_default_timezone_set($current_timezone); } } }
- First commit to issue fork.
- 🇮🇳India shalini_jha
I Have tried to add test coverage for now method.Moving this to NR , Kindly review & let me know if any updates are needed.
- 🇷🇺Russia Chi
There are a few concerns with the proposed approach.
1. The implementation is hardcoded to one specific date format (
Y-m-d\TH:i:s
).
2. It overlaps with PSR 20 wherenow()
is supposed to returnDateTimeImmutable
object.
3. It does not use core time service. So no way to mock the date in tests. - 🇩🇰Denmark Steven Snedker
@chi, your concers addressed
The implementation is hardcoded to one specific date format (Y-m-d\TH:i:s).
Yeah. It looks shoddy but
a) There is no core time format just like this
b) That is the time format that all datetime fields expect.Can you improve it?
It overlaps with PSR 20 where now() is supposed to return DateTimeImmutable object.
Let's call this useful function getNow then.
It does not use core time service. So no way to mock the date in tests.
Hm. Is that a necessity?
Can you point to (or have ChatGPT create) the specific tests @smustgrave wishes for? - 🇷🇺Russia Chi
That is the time format that all datetime fields expect.
Because it's storage format. It is rarely used in UI.
You may make the method more flexible by adding optional format parameter like follows.function getNow(string $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT)
Is that a necessity?
It's a general reason for having services injected. Core uses @datetime service when needs to obtain current time.
Hopefully, someday it'll switch from the custom baked
TimeInterface
to PSR-20. - 🇩🇰Denmark Steven Snedker
As per #19 ✨ Add a helper function: drupal_get_now() Active and #20 ✨ Add a helper function: drupal_get_now() Active I give up.
I will not spend any more time trying to port this enhancement correctly to Drupal core. It's just not worth the effort.
May all your potential upcoming rewriting, pushing and requesting be met with success, @sourav_paul and @shalini_jha.