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

Vo Date Time Laravel Package

awd-studio/vo-date-time

Immutable PHP 8.3+ date-time value object. Create from strings, compare (equal/greater/less/between), and return new instances for changes like nextDay(), copy(), or modified() with DateTimePeriod offsets (days, minutes, weeks).

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require awd-studio/vo-date-time
    

    Ensure your project uses PHP 8.3+ (check php -v and composer.json constraints).

  2. First Use Case: Replace a mutable DateTime or Carbon instance in a critical comparison. Example:

    // Before (mutable, risky)
    $dueDate = new DateTime('2024-12-31');
    if ($dueDate > $now) { ... }
    
    // After (immutable, safe)
    $dueDate = DateTime::fromString('2024-12-31');
    if ($dueDate->isGreaterThan(DateTime::now())) { ... }
    
  3. Where to Look First:

    • DateTime class: Core immutable wrapper with comparison methods (isEqual, isBetween).
    • DateTimePeriod: Helper for modifications (e.g., days: 1).
    • README.md examples: Focus on fromString(), modified(), and comparison methods.

Implementation Patterns

Usage Patterns

1. Immutable Domain Models

Encapsulate dates in value objects within domain entities or DTOs:

class Order {
    public function __construct(
        private DateTime $createdAt,
        private DateTime $dueDate,
    ) {}

    public function isOverdue(): bool {
        return $this->dueDate->isLessThan(DateTime::now());
    }
}

2. Validation Layer

Extend Laravel’s validator to enforce VO constraints:

Validator::extend('after_vo', function ($attr, $value, $params) {
    $date = DateTime::fromString($value);
    $comparisonDate = DateTime::fromString($params[0]);
    return $date->isGreaterThan($comparisonDate);
});

// Usage:
$request->validate(['due_date' => 'required|after_vo:created_at']);

3. Service Layer Operations

Use DateTimePeriod for business logic:

class SubscriptionService {
    public function renew(Subscription $subscription): Subscription {
        $renewalDate = $subscription->endsAt->modified(
            new DateTimePeriod(months: 1)
        );
        return $subscription->renew($renewalDate);
    }
}

4. API Responses

Serialize VOs to ISO strings or timestamps:

class OrderResource extends JsonResource {
    public function toArray($request) {
        return [
            'due_date' => $this->resource->dueDate->toIsoString(),
            'is_overdue' => $this->resource->isOverdue(),
        ];
    }
}

5. Database Integration

Use accessors/casts for Eloquent:

class Order extends Model {
    protected $casts = [
        'created_at' => DateTime::class,
        'due_date' => DateTime::class,
    ];

    public function getDueDateAttribute($value) {
        return DateTime::fromTimestamp($value);
    }
}

Workflows

Migration from Carbon

  1. Replace Parsing:
    // Before
    $date = Carbon::parse($request->input('date'));
    
    // After
    $date = DateTime::fromString($request->input('date'));
    
  2. Replace Comparisons:
    // Before
    if ($date->gt($now)) { ... }
    
    // After
    if ($date->isGreaterThan(DateTime::now())) { ... }
    
  3. Replace Modifications:
    // Before
    $date->addDays(1);
    
    // After
    $date->modified(new DateTimePeriod(days: 1));
    

Hybrid Approach

Use VOs in domain logic while keeping Carbon for persistence/APIs:

// Domain layer (VO)
$dueDate = DateTime::fromString($request->due_date);

// Persistence layer (Carbon)
$model->due_date = $dueDate->toCarbon()->format('Y-m-d');

Integration Tips

  • Timezones: Explicitly set timezones during creation:
    $date = DateTime::fromString('2024-01-01', 'UTC');
    
  • Testing: Use VOs in unit tests for predictable state:
    $now = DateTime::fromString('2024-01-01');
    $this->assertTrue($now->isEqual($now->copy()));
    
  • Performance: Reuse DateTimePeriod objects for repeated operations:
    $oneDay = new DateTimePeriod(days: 1);
    $tomorrow = $date->modified($oneDay);
    

Gotchas and Tips

Pitfalls

  1. Immutability Overhead:

    • Issue: Methods like nextDay() return new instances, which can bloat memory for chained operations.
    • Fix: Cache intermediate results or use copy() sparingly.
  2. Timezone Defaults:

    • Issue: VOs default to the system timezone, which may differ from expectations (e.g., UTC).
    • Fix: Always specify timezones explicitly:
      DateTime::fromString('2024-01-01', 'America/New_York');
      
  3. No Built-in Serialization:

    • Issue: VOs don’t implement JsonSerializable or ArrayAccess by default.
    • Fix: Extend the class or add accessors:
      $date->toArray(); // Requires custom implementation.
      
  4. Database Quirks:

    • Issue: Eloquent casts may not work out-of-the-box with custom VOs.
    • Fix: Use accessors/mutators or a custom cast:
      protected $casts = [
          'due_date' => [DateTime::class, 'fromTimestamp'],
      ];
      
  5. Edge Cases in Comparisons:

    • Issue: isBetween() is inclusive (unlike some Carbon methods).
    • Fix: Document this behavior or create a helper:
      $date->isBetweenExclusive($start, $end);
      

Debugging

  • Tooling:

    • Use toCarbon() for debugging:
      dd($date->toCarbon()->format('Y-m-d H:i:sP'));
      
    • Leverage phpstan to catch invalid VO usage:
      // phpstan expects DateTime, not string
      $this->assertInstanceOf(DateTime::class, $date);
      
  • Common Errors:

    • InvalidArgumentException: Thrown for malformed date strings. Validate input first:
      try {
          $date = DateTime::fromString($input);
      } catch (InvalidArgumentException $e) {
          // Handle invalid date.
      }
      
    • Timezone Mismatches: Compare VOs with the same timezone:
      $date1 = DateTime::fromString('...', 'UTC');
      $date2 = DateTime::fromString('...', 'UTC');
      $date1->isEqual($date2); // Safe.
      

Tips

  1. Extension Points:

    • Add Custom Methods: Extend the class for domain-specific logic:
      class BusinessDateTime extends DateTime {
          public function isBusinessDay(): bool {
              return $this->format('N') <= 5; // Mon-Fri
          }
      }
      
    • Override modified(): For custom period logic:
      $date->modified(new CustomPeriod());
      
  2. Performance:

    • Avoid Chaining: Store intermediate results to prevent memory leaks:
      $tomorrow = $date->nextDay(); // New instance.
      $dayAfter = $tomorrow->nextDay(); // Another instance.
      
    • Use copy() Wisely: Only when you need a shallow copy (no new instance).
  3. Testing:

    • Freeze Time: Use DateTime::fromTimestamp() with fixed timestamps:
      $fixedDate = DateTime::fromTimestamp(1700000000);
      
    • Assertions: Prefer VO methods over Carbon:
      $this->assertTrue($date->isEqual($expectedDate));
      
  4. Laravel-Specific:

    • Carbon Interop: Convert to/from Carbon for legacy code:
      $carbon = $date->toCarbon();
      $date = DateTime::fromCarbon($carbon);
      
    • Request Validation: Use VOs in Form Requests:
      public function rules() {
          return [
              'due_date' => ['required', function ($attr, $value, $fail) {
      
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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor