Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Date Time Precision Laravel Package

digital-craftsman/date-time-precision

Thin PHP value objects for precise date/time concepts: Moment (UTC-backed) plus Time, Date, Month, Year, Day, Weekday and collections. Avoid misleading DateTime comparisons, handle timezone-safe modifications (DST), with Symfony normalizers and Doctrine types.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require digital-craftsman/date-time-precision:0.14.*
    

    Pin to a minor version to avoid breaking changes.

  2. Basic Usage: Replace \DateTime with Moment for precise time handling:

    use DigitalCraftsman\DateTimePrecision\Moment;
    use DigitalCraftsman\DateTimePrecision\Time;
    
    $now = Moment::now(); // UTC by default
    $openingTime = Time::fromString('09:00:00', 'Europe/Berlin');
    
  3. First Use Case: Validate business hours in a timezone-aware way:

    if ($now->isBeforeInTimeZone($openingTime, 'Europe/Berlin')) {
        throw new FacilityNotOpenException();
    }
    

Key Entry Points

  • Moment: Immutable wrapper for \DateTime (always UTC internally).
  • Precision Objects: Time, Date, Month, Year, Day, Days.
  • Doctrine Integration: Auto-registered types for ORM storage.
  • Clock Interface: SystemClock (default) or FrozenClock (testing).

Implementation Patterns

Core Workflows

1. Timezone-Aware Comparisons

// Compare a moment (UTC) with a time in a facility's timezone
if ($appointment->scheduledAt->isBeforeInTimeZone($facility->closingTime, $facility->timezone)) {
    // Valid appointment
}

2. Immutable Modifications

// Add 7 days in the facility's timezone (result remains UTC internally)
$newDeadline = $now->modifyInTimeZone('+7 days', 'Europe/Berlin');

3. Database Storage

#[ORM\Entity]
class Booking {
    #[ORM\Column(type: Moment::class)]
    public Moment $createdAt;

    #[ORM\Column(type: Time::class)]
    public Time $scheduledTime;
}

4. Testing with FrozenClock

// In tests, replace SystemClock with FrozenClock
$clock = new FrozenClock(Moment::fromString('2023-01-01'));
$now = $clock->now(); // Always returns 2023-01-01

Integration Tips

Laravel-Specific

  • Service Providers: No manual registration needed—Doctrine types auto-register.
  • Validation: Use with Laravel’s validation rules via custom rules:
    use DigitalCraftsman\DateTimePrecision\Date;
    
    class DateRule extends AbstractRule {
        public function passes($attribute, $value) {
            return Date::fromString($value)->isAfter(Date::today());
        }
    }
    
  • API Responses: Leverage Symfony normalizers for JSON serialization:
    return response()->json([
        'opening_time' => $facility->openingTime->format('H:i'),
    ]);
    

Common Patterns

  • Collections: Use Days or Weekdays for bulk operations:
    $holidays = Days::fromArray(['2023-12-25', '2023-12-26']);
    if ($date->isIn($holidays)) { /* ... */ }
    
  • Periods: Generate ranges between dates:
    $dates = $startDate->datesUntil($endDate);
    

Gotchas and Tips

Pitfalls

  1. Timezone Assumptions:

    • All Moment objects are UTC internally. Modifications in other timezones (e.g., modifyInTimeZone) return UTC Moments.
    • Gotcha: Forgetting to specify a timezone in comparisons:
      // ❌ Avoid: Assumes UTC!
      $now->isBefore($facility->openingTime); // May fail due to timezone mismatch
      
      Fix: Always pass the correct timezone:
      $now->isBeforeInTimeZone($facility->openingTime, $facility->timezone);
      
  2. Immutability:

    • All methods return new instances. Avoid chaining without assignment:
      // ❌ Silent no-op
      $now->modifyInTimeZone('+1 day', 'UTC');
      
      // ✅ Correct
      $tomorrow = $now->modifyInTimeZone('+1 day', 'UTC');
      
  3. Doctrine Quirks:

    • Column Types: Use the fully qualified class name (e.g., DigitalCraftsman\DateTimePrecision\Moment) in ORM mappings.
    • SQL Comments: Some methods (e.g., requiresSQLCommentHint()) are required for Doctrine 3.x compatibility.
  4. Deprecations:

    • Methods like isDateAfterInTimeZone() are deprecated in favor of isAfterInTimeZone($date, $timezone).
    • Check the UPGRADE.md for version-specific changes.

Debugging Tips

  • Clock Overrides: Use FrozenClock in tests to isolate time-dependent logic:
    $this->app->bind(Clock::class, fn() => new FrozenClock(Moment::fromString('2023-01-01')));
    
  • Timezone Validation: Log timezone strings to catch typos:
    \Log::debug('Timezone used:', ['timezone' => $facility->timezone]);
    
  • Normalization: Ensure digital-craftsman/self-aware-normalizers is installed for API responses:
    composer require digital-craftsman/self-aware-normalizers
    

Extension Points

  1. Custom Comparisons: Extend Moment or precision objects for domain-specific logic:

    class BusinessHours {
        public static function isWithin(Moment $moment, Time $open, Time $close, string $timezone) {
            return $moment->isBetweenInTimeZone($open, $close, $timezone);
        }
    }
    
  2. Doctrine Types: Create custom types for legacy systems:

    #[ORM\Column(type: 'string')] // Store as ISO string
    private string $legacyDateTime;
    
    // Convert on get/set
    public function getScheduledAt(): Moment {
        return Moment::fromString($this->legacyDateTime);
    }
    
  3. Clock Strategies: Implement Clock for custom time sources (e.g., API-based time):

    class ApiClock implements Clock {
        public function now(): Moment {
            return Moment::fromString(file_get_contents('https://api.time.com'));
        }
    }
    

Performance Notes

  • Database Storage: Prefer Moment over DateTime for UTC consistency, but benchmark storage size (e.g., TIMESTAMP vs. string).
  • Timezone Conversions: Avoid repeated conversions in loops—cache DateTimeZone objects:
    private DateTimeZone $berlinTz = new DateTimeZone('Europe/Berlin');
    

Configuration Quirks

  • PHP Timezone: The package ignores date.timezone in PHP.ini—all Moments are UTC.
  • Doctrine Auto-Registration: Ensure your config/packages/doctrine.yaml includes:
    doctrine:
        orm:
            mappings:
                datetime_precision:
                    type: attribute
                    prefix: 'DigitalCraftsman\DateTimePrecision'
                    dir: '%kernel.project_dir%/vendor/digital-craftsman/date-time-precision/src/Doctrine'
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager