4
4
5
5
namespace Psl \DateTime ;
6
6
7
- use Psl \Str ;
8
-
9
- use function getdate ;
10
- use function mktime ;
7
+ use IntlCalendar ;
8
+ use Psl \Locale \Locale ;
11
9
12
10
final class DateTime implements DateTimeInterface
13
11
{
@@ -87,24 +85,16 @@ private function __construct(Timezone $timezone, Timestamp $timestamp, int $year
87
85
}
88
86
89
87
/**
90
- * Creates a new DateTime instance representing the current moment.
91
- *
92
- * This static method returns a DateTime object set to the current date and time. If a specific timezone is
93
- * provided, the returned DateTime will be adjusted to reflect the date and time in that timezone. If no timezone
94
- * is specified, the system's default timezone is used.
88
+ * Creates a new {@see DateTime} instance representing the current moment.
95
89
*
96
- * Utilizing the {@see Timestamp::now()} method, this function captures the precise moment of invocation down to the
97
- * nanosecond. This ensures that the {@see DateTime] instance represents an accurate and precise point in time.
98
- *
99
- * The method optionally accepts a timezone. If omitted, the DateTime object is created using the default system
100
- * timezone, as determined by {@see Timezone::default()}. This flexibility allows for the creation of DateTime instances
101
- * that are relevant in various global contexts without the need for manual timezone conversion by the caller.
90
+ * This static method returns a {@see DateTime} object set to the current date and time. If a specific timezone is
91
+ * provided, the returned {@see DateTime} will be adjusted to reflect the date and time in that timezone.
102
92
*
103
93
* @pure
104
94
*/
105
- public static function now (? Timezone $ timezone = null ): DateTime
95
+ public static function now (Timezone $ timezone ): DateTime
106
96
{
107
- return self ::fromTimestamp (Timestamp::now (), $ timezone ?? Timezone:: default () );
97
+ return self ::fromTimestamp (Timestamp::now (), $ timezone );
108
98
}
109
99
110
100
/**
@@ -115,9 +105,6 @@ public static function now(?Timezone $timezone = null): DateTime
115
105
* method combines the current date context with a specific time, offering a convenient way to specify times such
116
106
* as "today at 14:00" in code.
117
107
*
118
- * If a timezone is provided, the {@see DateTime} object is adjusted to reflect the specified time in that timezone. If
119
- * the timezone parameter is omitted, the system's default timezone, as determined by {@see Timezone::default()}, is used.
120
- *
121
108
* The time components (hours, minutes, seconds, nanoseconds) must be within their valid ranges. The method
122
109
* enforces these constraints and throws an {@see Exception\InvalidArgumentException} if any component is out of bounds.
123
110
*
@@ -131,19 +118,15 @@ public static function now(?Timezone $timezone = null): DateTime
131
118
*
132
119
* @pure
133
120
*/
134
- public static function todayAt (int $ hours , int $ minutes , int $ seconds = 0 , int $ nanoseconds = 0 , ? Timezone $ timezone = null ): DateTime
121
+ public static function todayAt (Timezone $ timezone , int $ hours , int $ minutes , int $ seconds = 0 , int $ nanoseconds = 0 ): DateTime
135
122
{
136
123
return self ::now ($ timezone )->withTime ($ hours , $ minutes , $ seconds , $ nanoseconds );
137
124
}
138
125
139
126
/**
140
127
* Creates a {@see DateTime} instance from individual date and time components.
141
128
*
142
- * This method constructs a DateTime object based on the specified year, month, day, hour, minute, second, and nanosecond,
143
- * taking into account the specified or default timezone. It validates the date and time components to ensure they form
144
- * a valid date-time. If the components are invalid (e.g., 30th February), an {@see Exception\InvalidArgumentException} is thrown.
145
- *
146
- * In cases where the specified time occurs twice (such as during the end of daylight saving time), the earlier occurrence
129
+ * Note: In cases where the specified time occurs twice (such as during the end of daylight saving time), the earlier occurrence
147
130
* is returned. To obtain the later occurrence, you can adjust the returned instance using `->plusHours(1)`.
148
131
*
149
132
* @param Month|int<1, 12> $month
@@ -159,96 +142,113 @@ public static function todayAt(int $hours, int $minutes, int $seconds = 0, int $
159
142
*/
160
143
public static function fromParts (int $ year , Month |int $ month , int $ day , int $ hours = 0 , int $ minutes = 0 , int $ seconds = 0 , int $ nanoseconds = 0 , Timezone $ timezone = null ): self
161
144
{
162
- $ timezone ??= Timezone::default ();
145
+ if ($ month instanceof Month) {
146
+ $ month = $ month ->value ;
147
+ }
163
148
164
- return Internal \zone_override ( $ timezone , static function () use ( $ timezone , $ year , $ month , $ day , $ hours , $ minutes , $ seconds , $ nanoseconds ): DateTime {
165
- if ( $ month instanceof Month) {
166
- $ month = $ month -> value ;
167
- }
149
+ /** @var IntlCalendar $calendar */
150
+ $ calendar = IntlCalendar:: createInstance (
151
+ $ timezone === null ? null : Internal \to_intl_timezone ( $ timezone ),
152
+ );
168
153
169
- $ timestamp = Timestamp::fromRaw (
170
- mktime ($ hours , $ minutes , $ seconds , $ month , $ day , $ year ),
171
- $ nanoseconds ,
172
- );
154
+ $ calendar ->set ($ year , $ month - 1 , $ day , $ hours , $ minutes , $ seconds );
173
155
174
- $ ret = new DateTime (
175
- $ timezone ,
176
- $ timestamp ,
177
- $ year ,
178
- $ month ,
179
- $ day ,
180
- $ hours ,
181
- $ minutes ,
182
- $ seconds ,
183
- $ nanoseconds ,
156
+ // Validate the date-time components by comparing them to what was set
157
+ if (
158
+ !($ calendar ->get (IntlCalendar::FIELD_YEAR ) === $ year &&
159
+ ($ calendar ->get (IntlCalendar::FIELD_MONTH ) + 1 ) === $ month &&
160
+ $ calendar ->get (IntlCalendar::FIELD_DAY_OF_MONTH ) === $ day &&
161
+ $ calendar ->get (IntlCalendar::FIELD_HOUR_OF_DAY ) === $ hours &&
162
+ $ calendar ->get (IntlCalendar::FIELD_MINUTE ) === $ minutes &&
163
+ $ calendar ->get (IntlCalendar::FIELD_SECOND ) === $ seconds )
164
+ ) {
165
+ throw new Exception \InvalidArgumentException (
166
+ 'The given components do not form a valid date-time. ' ,
184
167
);
168
+ }
169
+
170
+ $ timestampInSeconds = (int ) ($ calendar ->getTime () / MILLISECONDS_PER_SECOND );
171
+ $ timestamp = Timestamp::fromRaw ($ timestampInSeconds , $ nanoseconds );
185
172
186
- // mktime() doesn't throw on invalid date/time, but silently returns a
187
- // timestamp that doesn't match the input; so we check for that here.
188
- if ( $ ret -> getParts () !== DateTime:: fromTimestamp ( $ timestamp , $ timezone )-> getParts ()) {
189
- throw new Exception \ InvalidArgumentException ( Str \format (
190
- ' The given components do not form a valid date-time in the timezone "%s" ' ,
191
- $ timezone -> value ,
192
- ));
193
- }
194
-
195
- return $ ret ;
196
- } );
173
+ return new self (
174
+ $ timezone ,
175
+ $ timestamp ,
176
+ $ year ,
177
+ $ month ,
178
+ $ day ,
179
+ $ hours ,
180
+ $ minutes ,
181
+ $ seconds ,
182
+ $ nanoseconds
183
+ );
197
184
}
198
185
199
186
/**
200
187
* Creates a {@see DateTime} instance from a timestamp, representing the same point in time.
201
188
*
202
189
* This method converts a {@see Timestamp} into a {@see DateTime} instance calculated for the specified timezone.
203
- * It extracts and uses the date and time parts corresponding to the given timestamp in the provided or default timezone.
204
- *
205
- * @param Timezone|null $timezone The timezone for the DateTime instance. Defaults to the system's default timezone if null.
206
190
*
207
191
* @pure
208
192
*/
209
- public static function fromTimestamp (Timestamp $ timestamp , ? Timezone $ timezone = null ): DateTime
193
+ public static function fromTimestamp (Timestamp $ timestamp , Timezone $ timezone ): DateTime
210
194
{
211
- $ timezone ??= Timezone::default ();
212
-
213
- return Internal \zone_override ($ timezone , static function () use ($ timezone , $ timestamp ): DateTime {
214
- [$ s , $ ns ] = $ timestamp ->toRaw ();
215
- $ parts = getdate ($ s );
216
-
217
- return new static (
218
- $ timezone ,
219
- $ timestamp ,
220
- $ parts ['year ' ],
221
- $ parts ['mon ' ],
222
- $ parts ['mday ' ],
223
- $ parts ['hours ' ],
224
- $ parts ['minutes ' ],
225
- $ parts ['seconds ' ],
226
- $ ns ,
227
- );
228
- });
195
+ /** @var IntlCalendar $calendar */
196
+ $ calendar = IntlCalendar::createInstance (
197
+ Internal \to_intl_timezone ($ timezone ),
198
+ );
199
+
200
+ $ calendar ->setTime (
201
+ $ timestamp ->getSeconds () * MILLISECONDS_PER_SECOND ,
202
+ );
203
+
204
+ $ year = $ calendar ->get (IntlCalendar::FIELD_YEAR );
205
+ $ month = $ calendar ->get (IntlCalendar::FIELD_MONTH ) + 1 ;
206
+ $ day = $ calendar ->get (IntlCalendar::FIELD_DAY_OF_MONTH );
207
+ $ hour = $ calendar ->get (IntlCalendar::FIELD_HOUR_OF_DAY );
208
+ $ minute = $ calendar ->get (IntlCalendar::FIELD_MINUTE );
209
+ $ second = $ calendar ->get (IntlCalendar::FIELD_SECOND );
210
+ $ nanoseconds = $ timestamp ->getNanoseconds ();
211
+
212
+ return new static (
213
+ $ timezone ,
214
+ $ timestamp ,
215
+ $ year ,
216
+ $ month ,
217
+ $ day ,
218
+ $ hour ,
219
+ $ minute ,
220
+ $ second ,
221
+ $ nanoseconds ,
222
+ );
229
223
}
230
224
231
225
/**
232
- * Parses a date-time string and returns a {@see DateTime} instance for the specified or default timezone .
226
+ * Creates a {@see DateTime} instance from a date/time string according to a specific pattern .
233
227
*
234
- * This method interprets a date-time string, potentially relative (e.g., "next Thursday", "10 minutes ago") or absolute
235
- * (e.g., "2023-03-15 12:00:00"), into a {@see DateTime} object. An optional base time (`$relative_to`) can be provided to resolve
236
- * relative date-time expressions. If `$relative_to` is not provided, the current system time is used as the base.
228
+ * This method allows parsing of date/time strings that conform to custom patterns,
229
+ * making it versatile for handling various date/time formats.
237
230
*
238
- * @param string $raw_string The date-time string to parse.
239
- * @param Timezone|null $timezone The timezone to use for the resulting DateTime instance. Defaults to the system's default timezone if null.
240
- * @param TemporalInterface|null $relative_to The temporal context used to interpret relative date-time expressions. Defaults to the current system time if null.
231
+ * @throws Exception\RuntimeException If parsing fails or the date/time string is invalid.
232
+ */
233
+ public static function fromPattern (string |DatePattern $ pattern , string $ raw_string , Timezone $ timezone , ?Locale $ locale = null ): self
234
+ {
235
+ return self ::fromTimestamp (
236
+ Timestamp::fromPattern ($ pattern , $ raw_string , $ timezone , $ locale ),
237
+ $ timezone ,
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Parses a date/time string into a {@see DateTime} instance.
241
243
*
242
- * @throws Exception\InvalidArgumentException If parsing fails or the date- time string is invalid .
244
+ * This method is a convenience wrapper for parsing date/ time strings without specifying a custom pattern .
243
245
*
244
- * @see https://www.php.net/manual/en/datetime.formats.php For information on supported date and time formats .
246
+ * @throws Exception\RuntimeException If parsing fails or the format of the date/ time string is invalid .
245
247
*/
246
- public static function parse (string $ raw_string , ? Timezone $ timezone = null , ?TemporalInterface $ relative_to = null ): self
248
+ public static function parse (string $ raw_string , Timezone $ timezone , ?Locale $ locale = null ): self
247
249
{
248
- $ timezone ??= Timezone::default ();
249
-
250
250
return self ::fromTimestamp (
251
- Timestamp::parse ($ raw_string , $ timezone , $ relative_to ),
251
+ Timestamp::parse ($ raw_string , $ timezone , $ locale ),
252
252
$ timezone ,
253
253
);
254
254
}
0 commit comments