Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/arrow/arrow.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

560 statements  

1""" 

2Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime`` 

3replacement. 

4 

5""" 

6 

7import calendar 

8import re 

9import sys 

10from datetime import date 

11from datetime import datetime as dt_datetime 

12from datetime import time as dt_time 

13from datetime import timedelta, timezone 

14from datetime import tzinfo as dt_tzinfo 

15from math import trunc 

16from time import struct_time 

17from typing import ( 

18 Any, 

19 ClassVar, 

20 Final, 

21 Generator, 

22 Iterable, 

23 List, 

24 Literal, 

25 Mapping, 

26 Optional, 

27 Tuple, 

28 Union, 

29 cast, 

30 overload, 

31) 

32 

33from dateutil import tz as dateutil_tz 

34from dateutil.relativedelta import relativedelta 

35 

36from arrow import formatter, locales, parser, util 

37from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES 

38from arrow.locales import TimeFrameLiteral 

39 

40TZ_EXPR = Union[dt_tzinfo, str] 

41 

42_T_FRAMES = Literal[ 

43 "year", 

44 "years", 

45 "month", 

46 "months", 

47 "day", 

48 "days", 

49 "hour", 

50 "hours", 

51 "minute", 

52 "minutes", 

53 "second", 

54 "seconds", 

55 "microsecond", 

56 "microseconds", 

57 "week", 

58 "weeks", 

59 "quarter", 

60 "quarters", 

61] 

62 

63_BOUNDS = Literal["[)", "()", "(]", "[]"] 

64 

65_GRANULARITY = Literal[ 

66 "auto", 

67 "second", 

68 "minute", 

69 "hour", 

70 "day", 

71 "week", 

72 "month", 

73 "quarter", 

74 "year", 

75] 

76 

77 

78class Arrow: 

79 """An :class:`Arrow <arrow.arrow.Arrow>` object. 

80 

81 Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing 

82 additional functionality. 

83 

84 :param year: the calendar year. 

85 :param month: the calendar month. 

86 :param day: the calendar day. 

87 :param hour: (optional) the hour. Defaults to 0. 

88 :param minute: (optional) the minute, Defaults to 0. 

89 :param second: (optional) the second, Defaults to 0. 

90 :param microsecond: (optional) the microsecond. Defaults to 0. 

91 :param tzinfo: (optional) A timezone expression. Defaults to UTC. 

92 :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0. 

93 

94 .. _tz-expr: 

95 

96 Recognized timezone expressions: 

97 

98 - A ``tzinfo`` object. 

99 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. 

100 - A ``str`` in ISO 8601 style, as in '+07:00'. 

101 - A ``str``, one of the following: 'local', 'utc', 'UTC'. 

102 

103 Usage:: 

104 

105 >>> import arrow 

106 >>> arrow.Arrow(2013, 5, 5, 12, 30, 45) 

107 <Arrow [2013-05-05T12:30:45+00:00]> 

108 

109 """ 

110 

111 resolution: ClassVar[timedelta] = dt_datetime.resolution 

112 min: ClassVar["Arrow"] 

113 max: ClassVar["Arrow"] 

114 

115 _ATTRS: Final[List[str]] = [ 

116 "year", 

117 "month", 

118 "day", 

119 "hour", 

120 "minute", 

121 "second", 

122 "microsecond", 

123 ] 

124 _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS] 

125 _MONTHS_PER_QUARTER: Final[int] = 3 

126 _SECS_PER_MINUTE: Final[int] = 60 

127 _SECS_PER_HOUR: Final[int] = 60 * 60 

128 _SECS_PER_DAY: Final[int] = 60 * 60 * 24 

129 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 

130 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 

131 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 

132 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 

133 

134 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { 

135 "second": 1.0, 

136 "minute": _SECS_PER_MINUTE, 

137 "hour": _SECS_PER_HOUR, 

138 "day": _SECS_PER_DAY, 

139 "week": _SECS_PER_WEEK, 

140 "month": _SECS_PER_MONTH, 

141 "quarter": _SECS_PER_QUARTER, 

142 "year": _SECS_PER_YEAR, 

143 } 

144 

145 _datetime: dt_datetime 

146 

147 def __init__( 

148 self, 

149 year: int, 

150 month: int, 

151 day: int, 

152 hour: int = 0, 

153 minute: int = 0, 

154 second: int = 0, 

155 microsecond: int = 0, 

156 tzinfo: Optional[TZ_EXPR] = None, 

157 **kwargs: Any, 

158 ) -> None: 

159 if tzinfo is None: 

160 tzinfo = dateutil_tz.tzutc() 

161 # detect that tzinfo is a pytz object (issue #626) 

162 elif ( 

163 isinstance(tzinfo, dt_tzinfo) 

164 and hasattr(tzinfo, "localize") 

165 and hasattr(tzinfo, "zone") 

166 and tzinfo.zone 

167 ): 

168 tzinfo = parser.TzinfoParser.parse(tzinfo.zone) 

169 elif isinstance(tzinfo, str): 

170 tzinfo = parser.TzinfoParser.parse(tzinfo) 

171 

172 fold = kwargs.get("fold", 0) 

173 

174 self._datetime = dt_datetime( 

175 year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold 

176 ) 

177 

178 # factories: single object, both original and from datetime. 

179 

180 @classmethod 

181 def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow": 

182 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given 

183 timezone. 

184 

185 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. 

186 

187 Usage:: 

188 

189 >>> arrow.now('Asia/Baku') 

190 <Arrow [2019-01-24T20:26:31.146412+04:00]> 

191 

192 """ 

193 

194 if tzinfo is None: 

195 tzinfo = dateutil_tz.tzlocal() 

196 

197 dt = dt_datetime.now(tzinfo) 

198 

199 return cls( 

200 dt.year, 

201 dt.month, 

202 dt.day, 

203 dt.hour, 

204 dt.minute, 

205 dt.second, 

206 dt.microsecond, 

207 dt.tzinfo, 

208 fold=getattr(dt, "fold", 0), 

209 ) 

210 

211 @classmethod 

212 def utcnow(cls) -> "Arrow": 

213 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC 

214 time. 

215 

216 Usage:: 

217 

218 >>> arrow.utcnow() 

219 <Arrow [2019-01-24T16:31:40.651108+00:00]> 

220 

221 """ 

222 

223 dt = dt_datetime.now(dateutil_tz.tzutc()) 

224 

225 return cls( 

226 dt.year, 

227 dt.month, 

228 dt.day, 

229 dt.hour, 

230 dt.minute, 

231 dt.second, 

232 dt.microsecond, 

233 dt.tzinfo, 

234 fold=getattr(dt, "fold", 0), 

235 ) 

236 

237 @classmethod 

238 def fromtimestamp( 

239 cls, 

240 timestamp: Union[int, float, str], 

241 tzinfo: Optional[TZ_EXPR] = None, 

242 ) -> "Arrow": 

243 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to 

244 the given timezone. 

245 

246 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. 

247 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. 

248 

249 """ 

250 

251 if tzinfo is None: 

252 tzinfo = dateutil_tz.tzlocal() 

253 elif isinstance(tzinfo, str): 

254 tzinfo = parser.TzinfoParser.parse(tzinfo) 

255 

256 if not util.is_timestamp(timestamp): 

257 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") 

258 

259 timestamp = util.normalize_timestamp(float(timestamp)) 

260 dt = dt_datetime.fromtimestamp(timestamp, tzinfo) 

261 

262 return cls( 

263 dt.year, 

264 dt.month, 

265 dt.day, 

266 dt.hour, 

267 dt.minute, 

268 dt.second, 

269 dt.microsecond, 

270 dt.tzinfo, 

271 fold=getattr(dt, "fold", 0), 

272 ) 

273 

274 @classmethod 

275 def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": 

276 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time. 

277 

278 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. 

279 

280 """ 

281 

282 if not util.is_timestamp(timestamp): 

283 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") 

284 

285 timestamp = util.normalize_timestamp(float(timestamp)) 

286 dt = dt_datetime.utcfromtimestamp(timestamp) 

287 

288 return cls( 

289 dt.year, 

290 dt.month, 

291 dt.day, 

292 dt.hour, 

293 dt.minute, 

294 dt.second, 

295 dt.microsecond, 

296 dateutil_tz.tzutc(), 

297 fold=getattr(dt, "fold", 0), 

298 ) 

299 

300 @classmethod 

301 def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": 

302 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and 

303 optional replacement timezone. 

304 

305 :param dt: the ``datetime`` 

306 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s 

307 timezone, or UTC if naive. 

308 

309 Usage:: 

310 

311 >>> dt 

312 datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific')) 

313 >>> arrow.Arrow.fromdatetime(dt) 

314 <Arrow [2021-04-07T13:48:00-07:00]> 

315 

316 """ 

317 

318 if tzinfo is None: 

319 if dt.tzinfo is None: 

320 tzinfo = dateutil_tz.tzutc() 

321 else: 

322 tzinfo = dt.tzinfo 

323 

324 return cls( 

325 dt.year, 

326 dt.month, 

327 dt.day, 

328 dt.hour, 

329 dt.minute, 

330 dt.second, 

331 dt.microsecond, 

332 tzinfo, 

333 fold=getattr(dt, "fold", 0), 

334 ) 

335 

336 @classmethod 

337 def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": 

338 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional 

339 replacement timezone. All time values are set to 0. 

340 

341 :param date: the ``date`` 

342 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC. 

343 

344 """ 

345 

346 if tzinfo is None: 

347 tzinfo = dateutil_tz.tzutc() 

348 

349 return cls(date.year, date.month, date.day, tzinfo=tzinfo) 

350 

351 @classmethod 

352 def strptime( 

353 cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None 

354 ) -> "Arrow": 

355 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format, 

356 in the style of ``datetime.strptime``. Optionally replaces the parsed timezone. 

357 

358 :param date_str: the date string. 

359 :param fmt: the format string using datetime format codes. 

360 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed 

361 timezone if ``fmt`` contains a timezone directive, otherwise UTC. 

362 

363 Usage:: 

364 

365 >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S') 

366 <Arrow [2019-01-20T15:49:10+00:00]> 

367 

368 """ 

369 

370 dt = dt_datetime.strptime(date_str, fmt) 

371 if tzinfo is None: 

372 tzinfo = dt.tzinfo 

373 

374 return cls( 

375 dt.year, 

376 dt.month, 

377 dt.day, 

378 dt.hour, 

379 dt.minute, 

380 dt.second, 

381 dt.microsecond, 

382 tzinfo, 

383 fold=getattr(dt, "fold", 0), 

384 ) 

385 

386 @classmethod 

387 def fromordinal(cls, ordinal: int) -> "Arrow": 

388 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding 

389 to the Gregorian Ordinal. 

390 

391 :param ordinal: an ``int`` corresponding to a Gregorian Ordinal. 

392 

393 Usage:: 

394 

395 >>> arrow.fromordinal(737741) 

396 <Arrow [2020-11-12T00:00:00+00:00]> 

397 

398 """ 

399 

400 util.validate_ordinal(ordinal) 

401 dt = dt_datetime.fromordinal(ordinal) 

402 return cls( 

403 dt.year, 

404 dt.month, 

405 dt.day, 

406 dt.hour, 

407 dt.minute, 

408 dt.second, 

409 dt.microsecond, 

410 dt.tzinfo, 

411 fold=getattr(dt, "fold", 0), 

412 ) 

413 

414 # factories: ranges and spans 

415 

416 @classmethod 

417 def range( 

418 cls, 

419 frame: _T_FRAMES, 

420 start: Union["Arrow", dt_datetime], 

421 end: Union["Arrow", dt_datetime, None] = None, 

422 tz: Optional[TZ_EXPR] = None, 

423 limit: Optional[int] = None, 

424 ) -> Generator["Arrow", None, None]: 

425 """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing 

426 points in time between two inputs. 

427 

428 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

429 :param start: A datetime expression, the start of the range. 

430 :param end: (optional) A datetime expression, the end of the range. 

431 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to 

432 ``start``'s timezone, or UTC if ``start`` is naive. 

433 :param limit: (optional) A maximum number of tuples to return. 

434 

435 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to 

436 return the entire range. Call with ``limit`` alone to return a maximum # of results from 

437 the start. Call with both to cap a range at a maximum # of results. 

438 

439 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before 

440 iterating. As such, either call with naive objects and ``tz``, or aware objects from the 

441 same timezone and no ``tz``. 

442 

443 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. 

444 

445 Recognized datetime expressions: 

446 

447 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

448 - A ``datetime`` object. 

449 

450 Usage:: 

451 

452 >>> start = datetime(2013, 5, 5, 12, 30) 

453 >>> end = datetime(2013, 5, 5, 17, 15) 

454 >>> for r in arrow.Arrow.range('hour', start, end): 

455 ... print(repr(r)) 

456 ... 

457 <Arrow [2013-05-05T12:30:00+00:00]> 

458 <Arrow [2013-05-05T13:30:00+00:00]> 

459 <Arrow [2013-05-05T14:30:00+00:00]> 

460 <Arrow [2013-05-05T15:30:00+00:00]> 

461 <Arrow [2013-05-05T16:30:00+00:00]> 

462 

463 **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator:: 

464 

465 >>> start = datetime(2013, 5, 5, 12, 30) 

466 >>> end = datetime(2013, 5, 5, 13, 30) 

467 >>> for r in arrow.Arrow.range('hour', start, end): 

468 ... print(repr(r)) 

469 ... 

470 <Arrow [2013-05-05T12:30:00+00:00]> 

471 <Arrow [2013-05-05T13:30:00+00:00]> 

472 

473 """ 

474 

475 _, frame_relative, relative_steps = cls._get_frames(frame) 

476 

477 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) 

478 

479 start = cls._get_datetime(start).replace(tzinfo=tzinfo) 

480 end, limit = cls._get_iteration_params(end, limit) 

481 end = cls._get_datetime(end).replace(tzinfo=tzinfo) 

482 

483 current = cls.fromdatetime(start) 

484 original_day = start.day 

485 day_is_clipped = False 

486 i = 0 

487 

488 while current <= end and i < limit: 

489 i += 1 

490 yield current 

491 

492 values = [getattr(current, f) for f in cls._ATTRS] 

493 current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc] 

494 check_imaginary=True, **{frame_relative: relative_steps} 

495 ) 

496 

497 if frame in ["month", "quarter", "year"] and current.day < original_day: 

498 day_is_clipped = True 

499 

500 if day_is_clipped and not cls._is_last_day_of_month(current): 

501 current = current.replace(day=original_day) 

502 

503 def span( 

504 self, 

505 frame: _T_FRAMES, 

506 count: int = 1, 

507 bounds: _BOUNDS = "[)", 

508 exact: bool = False, 

509 week_start: int = 1, 

510 ) -> Tuple["Arrow", "Arrow"]: 

511 """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan 

512 of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

513 

514 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

515 :param count: (optional) the number of frames to span. 

516 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

517 whether to include or exclude the start and end values in the span. '(' excludes 

518 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

519 If the bounds are not specified, the default bound '[)' is used. 

520 :param exact: (optional) whether to have the start of the timespan begin exactly 

521 at the time specified by ``start`` and the end of the timespan truncated 

522 so as not to extend beyond ``end``. 

523 :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where 

524 Monday is 1 and Sunday is 7. 

525 

526 Supported frame values: year, quarter, month, week, day, hour, minute, second. 

527 

528 Usage:: 

529 

530 >>> arrow.utcnow() 

531 <Arrow [2013-05-09T03:32:36.186203+00:00]> 

532 

533 >>> arrow.utcnow().span('hour') 

534 (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>) 

535 

536 >>> arrow.utcnow().span('day') 

537 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>) 

538 

539 >>> arrow.utcnow().span('day', count=2) 

540 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>) 

541 

542 >>> arrow.utcnow().span('day', bounds='[]') 

543 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>) 

544 

545 >>> arrow.utcnow().span('week') 

546 (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>) 

547 

548 >>> arrow.utcnow().span('week', week_start=6) 

549 (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>) 

550 

551 """ 

552 if not 1 <= week_start <= 7: 

553 raise ValueError("week_start argument must be between 1 and 7.") 

554 

555 util.validate_bounds(bounds) 

556 

557 frame_absolute, frame_relative, relative_steps = self._get_frames(frame) 

558 

559 if frame_absolute == "week": 

560 attr = "day" 

561 elif frame_absolute == "quarter": 

562 attr = "month" 

563 else: 

564 attr = frame_absolute 

565 

566 floor = self 

567 if not exact: 

568 index = self._ATTRS.index(attr) 

569 frames = self._ATTRS[: index + 1] 

570 

571 values = [getattr(self, f) for f in frames] 

572 

573 for _ in range(3 - len(values)): 

574 values.append(1) 

575 

576 floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] 

577 

578 if frame_absolute == "week": 

579 # if week_start is greater than self.isoweekday() go back one week by setting delta = 7 

580 delta = 7 if week_start > self.isoweekday() else 0 

581 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta) 

582 elif frame_absolute == "quarter": 

583 floor = floor.shift(months=-((self.month - 1) % 3)) 

584 

585 ceil = floor.shift( 

586 check_imaginary=True, **{frame_relative: count * relative_steps} 

587 ) 

588 

589 if bounds[0] == "(": 

590 floor = floor.shift(microseconds=+1) 

591 

592 if bounds[1] == ")": 

593 ceil = ceil.shift(microseconds=-1) 

594 

595 return floor, ceil 

596 

597 def floor(self, frame: _T_FRAMES) -> "Arrow": 

598 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor" 

599 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

600 Equivalent to the first element in the 2-tuple returned by 

601 :func:`span <arrow.arrow.Arrow.span>`. 

602 

603 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

604 

605 Usage:: 

606 

607 >>> arrow.utcnow().floor('hour') 

608 <Arrow [2013-05-09T03:00:00+00:00]> 

609 

610 """ 

611 

612 return self.span(frame)[0] 

613 

614 def ceil(self, frame: _T_FRAMES) -> "Arrow": 

615 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling" 

616 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

617 Equivalent to the second element in the 2-tuple returned by 

618 :func:`span <arrow.arrow.Arrow.span>`. 

619 

620 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

621 

622 Usage:: 

623 

624 >>> arrow.utcnow().ceil('hour') 

625 <Arrow [2013-05-09T03:59:59.999999+00:00]> 

626 

627 """ 

628 

629 return self.span(frame)[1] 

630 

631 @classmethod 

632 def span_range( 

633 cls, 

634 frame: _T_FRAMES, 

635 start: dt_datetime, 

636 end: dt_datetime, 

637 tz: Optional[TZ_EXPR] = None, 

638 limit: Optional[int] = None, 

639 bounds: _BOUNDS = "[)", 

640 exact: bool = False, 

641 ) -> Iterable[Tuple["Arrow", "Arrow"]]: 

642 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, 

643 representing a series of timespans between two inputs. 

644 

645 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

646 :param start: A datetime expression, the start of the range. 

647 :param end: (optional) A datetime expression, the end of the range. 

648 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to 

649 ``start``'s timezone, or UTC if ``start`` is naive. 

650 :param limit: (optional) A maximum number of tuples to return. 

651 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

652 whether to include or exclude the start and end values in each span in the range. '(' excludes 

653 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

654 If the bounds are not specified, the default bound '[)' is used. 

655 :param exact: (optional) whether to have the first timespan start exactly 

656 at the time specified by ``start`` and the final span truncated 

657 so as not to extend beyond ``end``. 

658 

659 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to 

660 return the entire range. Call with ``limit`` alone to return a maximum # of results from 

661 the start. Call with both to cap a range at a maximum # of results. 

662 

663 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before 

664 iterating. As such, either call with naive objects and ``tz``, or aware objects from the 

665 same timezone and no ``tz``. 

666 

667 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. 

668 

669 Recognized datetime expressions: 

670 

671 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

672 - A ``datetime`` object. 

673 

674 **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned 

675 iterator of timespans. 

676 

677 Usage: 

678 

679 >>> start = datetime(2013, 5, 5, 12, 30) 

680 >>> end = datetime(2013, 5, 5, 17, 15) 

681 >>> for r in arrow.Arrow.span_range('hour', start, end): 

682 ... print(r) 

683 ... 

684 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>) 

685 (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) 

686 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>) 

687 (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) 

688 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>) 

689 (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>) 

690 

691 """ 

692 

693 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) 

694 start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0] 

695 end = cls.fromdatetime(end, tzinfo) 

696 _range = cls.range(frame, start, end, tz, limit) 

697 if not exact: 

698 for r in _range: 

699 yield r.span(frame, bounds=bounds, exact=exact) 

700 

701 for r in _range: 

702 floor, ceil = r.span(frame, bounds=bounds, exact=exact) 

703 if ceil > end: 

704 ceil = end 

705 if bounds[1] == ")": 

706 ceil += relativedelta(microseconds=-1) 

707 if floor == end: 

708 break 

709 elif floor + relativedelta(microseconds=-1) == end: 

710 break 

711 yield floor, ceil 

712 

713 @classmethod 

714 def interval( 

715 cls, 

716 frame: _T_FRAMES, 

717 start: dt_datetime, 

718 end: dt_datetime, 

719 interval: int = 1, 

720 tz: Optional[TZ_EXPR] = None, 

721 bounds: _BOUNDS = "[)", 

722 exact: bool = False, 

723 ) -> Iterable[Tuple["Arrow", "Arrow"]]: 

724 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, 

725 representing a series of intervals between two inputs. 

726 

727 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

728 :param start: A datetime expression, the start of the range. 

729 :param end: (optional) A datetime expression, the end of the range. 

730 :param interval: (optional) Time interval for the given time frame. 

731 :param tz: (optional) A timezone expression. Defaults to UTC. 

732 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

733 whether to include or exclude the start and end values in the intervals. '(' excludes 

734 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

735 If the bounds are not specified, the default bound '[)' is used. 

736 :param exact: (optional) whether to have the first timespan start exactly 

737 at the time specified by ``start`` and the final interval truncated 

738 so as not to extend beyond ``end``. 

739 

740 Supported frame values: year, quarter, month, week, day, hour, minute, second 

741 

742 Recognized datetime expressions: 

743 

744 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

745 - A ``datetime`` object. 

746 

747 Recognized timezone expressions: 

748 

749 - A ``tzinfo`` object. 

750 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. 

751 - A ``str`` in ISO 8601 style, as in '+07:00'. 

752 - A ``str``, one of the following: 'local', 'utc', 'UTC'. 

753 

754 Usage: 

755 

756 >>> start = datetime(2013, 5, 5, 12, 30) 

757 >>> end = datetime(2013, 5, 5, 17, 15) 

758 >>> for r in arrow.Arrow.interval('hour', start, end, 2): 

759 ... print(r) 

760 ... 

761 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) 

762 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) 

763 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>) 

764 """ 

765 if interval < 1: 

766 raise ValueError("interval has to be a positive integer") 

767 

768 spanRange = iter( 

769 cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact) 

770 ) 

771 while True: 

772 try: 

773 intvlStart, intvlEnd = next(spanRange) 

774 for _ in range(interval - 1): 

775 try: 

776 _, intvlEnd = next(spanRange) 

777 except StopIteration: 

778 continue 

779 yield intvlStart, intvlEnd 

780 except StopIteration: 

781 return 

782 

783 # representations 

784 

785 def __repr__(self) -> str: 

786 return f"<{self.__class__.__name__} [{self.__str__()}]>" 

787 

788 def __str__(self) -> str: 

789 return self._datetime.isoformat() 

790 

791 def __format__(self, formatstr: str) -> str: 

792 if len(formatstr) > 0: 

793 return self.format(formatstr) 

794 

795 return str(self) 

796 

797 def __hash__(self) -> int: 

798 return self._datetime.__hash__() 

799 

800 # attributes and properties 

801 

802 def __getattr__(self, name: str) -> Any: 

803 if name == "week": 

804 return self.isocalendar()[1] 

805 

806 if name == "quarter": 

807 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1 

808 

809 if not name.startswith("_"): 

810 value: Optional[Any] = getattr(self._datetime, name, None) 

811 

812 if value is not None: 

813 return value 

814 

815 return cast(int, object.__getattribute__(self, name)) 

816 

817 @property 

818 def tzinfo(self) -> dt_tzinfo: 

819 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object. 

820 

821 Usage:: 

822 

823 >>> arw=arrow.utcnow() 

824 >>> arw.tzinfo 

825 tzutc() 

826 

827 """ 

828 

829 # In Arrow, `_datetime` cannot be naive. 

830 return cast(dt_tzinfo, self._datetime.tzinfo) 

831 

832 @property 

833 def datetime(self) -> dt_datetime: 

834 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object. 

835 

836 Usage:: 

837 

838 >>> arw=arrow.utcnow() 

839 >>> arw.datetime 

840 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc()) 

841 

842 """ 

843 

844 return self._datetime 

845 

846 @property 

847 def naive(self) -> dt_datetime: 

848 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` 

849 object. 

850 

851 Usage:: 

852 

853 >>> nairobi = arrow.now('Africa/Nairobi') 

854 >>> nairobi 

855 <Arrow [2019-01-23T19:27:12.297999+03:00]> 

856 >>> nairobi.naive 

857 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999) 

858 

859 """ 

860 

861 return self._datetime.replace(tzinfo=None) 

862 

863 def timestamp(self) -> float: 

864 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in 

865 UTC time. 

866 

867 Usage:: 

868 

869 >>> arrow.utcnow().timestamp() 

870 1616882340.256501 

871 

872 """ 

873 

874 return self._datetime.timestamp() 

875 

876 @property 

877 def int_timestamp(self) -> int: 

878 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in 

879 UTC time. 

880 

881 Usage:: 

882 

883 >>> arrow.utcnow().int_timestamp 

884 1548260567 

885 

886 """ 

887 

888 return int(self.timestamp()) 

889 

890 @property 

891 def float_timestamp(self) -> float: 

892 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` 

893 object, in UTC time. 

894 

895 Usage:: 

896 

897 >>> arrow.utcnow().float_timestamp 

898 1548260516.830896 

899 

900 """ 

901 

902 return self.timestamp() 

903 

904 @property 

905 def fold(self) -> int: 

906 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object.""" 

907 

908 return self._datetime.fold 

909 

910 @property 

911 def ambiguous(self) -> bool: 

912 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current 

913 timezone. 

914 

915 """ 

916 

917 return dateutil_tz.datetime_ambiguous(self._datetime) 

918 

919 @property 

920 def imaginary(self) -> bool: 

921 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone.""" 

922 

923 return not dateutil_tz.datetime_exists(self._datetime) 

924 

925 # mutation and duplication. 

926 

927 def clone(self) -> "Arrow": 

928 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one. 

929 

930 Usage: 

931 

932 >>> arw = arrow.utcnow() 

933 >>> cloned = arw.clone() 

934 

935 """ 

936 

937 return self.fromdatetime(self._datetime) 

938 

939 def replace(self, **kwargs: Any) -> "Arrow": 

940 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated 

941 according to inputs. 

942 

943 Use property names to set their value absolutely:: 

944 

945 >>> import arrow 

946 >>> arw = arrow.utcnow() 

947 >>> arw 

948 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

949 >>> arw.replace(year=2014, month=6) 

950 <Arrow [2014-06-11T22:27:34.787885+00:00]> 

951 

952 You can also replace the timezone without conversion, using a 

953 :ref:`timezone expression <tz-expr>`:: 

954 

955 >>> arw.replace(tzinfo=tz.tzlocal()) 

956 <Arrow [2013-05-11T22:27:34.787885-07:00]> 

957 

958 """ 

959 

960 absolute_kwargs = {} 

961 

962 for key, value in kwargs.items(): 

963 if key in self._ATTRS: 

964 absolute_kwargs[key] = value 

965 elif key in ["week", "quarter"]: 

966 raise ValueError(f"Setting absolute {key} is not supported.") 

967 elif key not in ["tzinfo", "fold"]: 

968 raise ValueError(f"Unknown attribute: {key!r}.") 

969 

970 current = self._datetime.replace(**absolute_kwargs) 

971 

972 tzinfo = kwargs.get("tzinfo") 

973 

974 if tzinfo is not None: 

975 tzinfo = self._get_tzinfo(tzinfo) 

976 current = current.replace(tzinfo=tzinfo) 

977 

978 fold = kwargs.get("fold") 

979 

980 if fold is not None: 

981 current = current.replace(fold=fold) 

982 

983 return self.fromdatetime(current) 

984 

985 def shift(self, check_imaginary: bool = True, **kwargs: Any) -> "Arrow": 

986 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated 

987 according to inputs. 

988 

989 Parameters: 

990 check_imaginary (bool): If True (default), will check for and resolve 

991 imaginary times (like during DST transitions). If False, skips this check. 

992 

993 

994 Use pluralized property names to relatively shift their current value: 

995 

996 >>> import arrow 

997 >>> arw = arrow.utcnow() 

998 >>> arw 

999 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

1000 >>> arw.shift(years=1, months=-1) 

1001 <Arrow [2014-04-11T22:27:34.787885+00:00]> 

1002 

1003 Day-of-the-week relative shifting can use either Python's weekday numbers 

1004 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's 

1005 day instances (MO, TU .. SU). When using weekday numbers, the returned 

1006 date will always be greater than or equal to the starting date. 

1007 

1008 Using the above code (which is a Saturday) and asking it to shift to Saturday: 

1009 

1010 >>> arw.shift(weekday=5) 

1011 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

1012 

1013 While asking for a Monday: 

1014 

1015 >>> arw.shift(weekday=0) 

1016 <Arrow [2013-05-13T22:27:34.787885+00:00]> 

1017 

1018 """ 

1019 

1020 relative_kwargs = {} 

1021 additional_attrs = ["weeks", "quarters", "weekday"] 

1022 

1023 for key, value in kwargs.items(): 

1024 if key in self._ATTRS_PLURAL or key in additional_attrs: 

1025 relative_kwargs[key] = value 

1026 else: 

1027 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs) 

1028 raise ValueError( 

1029 f"Invalid shift time frame. Please select one of the following: {supported_attr}." 

1030 ) 

1031 

1032 # core datetime does not support quarters, translate to months. 

1033 relative_kwargs.setdefault("months", 0) 

1034 relative_kwargs["months"] += ( 

1035 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER 

1036 ) 

1037 

1038 current = self._datetime + relativedelta(**relative_kwargs) 

1039 

1040 # If check_imaginary is True, perform the check for imaginary times (DST transitions) 

1041 if check_imaginary and not dateutil_tz.datetime_exists(current): 

1042 current = dateutil_tz.resolve_imaginary(current) 

1043 

1044 return self.fromdatetime(current) 

1045 

1046 def to(self, tz: TZ_EXPR) -> "Arrow": 

1047 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted 

1048 to the target timezone. 

1049 

1050 :param tz: A :ref:`timezone expression <tz-expr>`. 

1051 

1052 Usage:: 

1053 

1054 >>> utc = arrow.utcnow() 

1055 >>> utc 

1056 <Arrow [2013-05-09T03:49:12.311072+00:00]> 

1057 

1058 >>> utc.to('US/Pacific') 

1059 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1060 

1061 >>> utc.to(tz.tzlocal()) 

1062 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1063 

1064 >>> utc.to('-07:00') 

1065 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1066 

1067 >>> utc.to('local') 

1068 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1069 

1070 >>> utc.to('local').to('utc') 

1071 <Arrow [2013-05-09T03:49:12.311072+00:00]> 

1072 

1073 """ 

1074 

1075 if not isinstance(tz, dt_tzinfo): 

1076 tz = parser.TzinfoParser.parse(tz) 

1077 

1078 dt = self._datetime.astimezone(tz) 

1079 

1080 return self.__class__( 

1081 dt.year, 

1082 dt.month, 

1083 dt.day, 

1084 dt.hour, 

1085 dt.minute, 

1086 dt.second, 

1087 dt.microsecond, 

1088 dt.tzinfo, 

1089 fold=getattr(dt, "fold", 0), 

1090 ) 

1091 

1092 # string output and formatting 

1093 

1094 def format( 

1095 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE 

1096 ) -> str: 

1097 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object, 

1098 formatted according to the provided format string. For a list of formatting values, 

1099 see :ref:`supported-tokens` 

1100 

1101 :param fmt: the format string. 

1102 :param locale: the locale to format. 

1103 

1104 Usage:: 

1105 

1106 >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ') 

1107 '2013-05-09 03:56:47 -00:00' 

1108 

1109 >>> arrow.utcnow().format('X') 

1110 '1368071882' 

1111 

1112 >>> arrow.utcnow().format('MMMM DD, YYYY') 

1113 'May 09, 2013' 

1114 

1115 >>> arrow.utcnow().format() 

1116 '2013-05-09 03:56:47 -00:00' 

1117 

1118 """ 

1119 

1120 return formatter.DateTimeFormatter(locale).format(self._datetime, fmt) 

1121 

1122 def humanize( 

1123 self, 

1124 other: Union["Arrow", dt_datetime, None] = None, 

1125 locale: str = DEFAULT_LOCALE, 

1126 only_distance: bool = False, 

1127 granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto", 

1128 ) -> str: 

1129 """Returns a localized, humanized representation of a relative difference in time. 

1130 

1131 :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object. 

1132 Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone. 

1133 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. 

1134 :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part. 

1135 :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute', 

1136 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings 

1137 

1138 Usage:: 

1139 

1140 >>> earlier = arrow.utcnow().shift(hours=-2) 

1141 >>> earlier.humanize() 

1142 '2 hours ago' 

1143 

1144 >>> later = earlier.shift(hours=4) 

1145 >>> later.humanize(earlier) 

1146 'in 4 hours' 

1147 

1148 """ 

1149 

1150 locale_name = locale 

1151 locale = locales.get_locale(locale) 

1152 

1153 if other is None: 

1154 utc = dt_datetime.now(timezone.utc).replace(tzinfo=dateutil_tz.tzutc()) 

1155 dt = utc.astimezone(self._datetime.tzinfo) 

1156 

1157 elif isinstance(other, Arrow): 

1158 dt = other._datetime 

1159 

1160 elif isinstance(other, dt_datetime): 

1161 if other.tzinfo is None: 

1162 dt = other.replace(tzinfo=self._datetime.tzinfo) 

1163 else: 

1164 dt = other.astimezone(self._datetime.tzinfo) 

1165 

1166 else: 

1167 raise TypeError( 

1168 f"Invalid 'other' argument of type {type(other).__name__!r}. " 

1169 "Argument must be of type None, Arrow, or datetime." 

1170 ) 

1171 

1172 if isinstance(granularity, list) and len(granularity) == 1: 

1173 granularity = granularity[0] 

1174 

1175 _delta = int(round((self._datetime - dt).total_seconds())) 

1176 sign = -1 if _delta < 0 else 1 

1177 delta_second = diff = abs(_delta) 

1178 

1179 try: 

1180 if granularity == "auto": 

1181 if diff < 10: 

1182 return locale.describe("now", only_distance=only_distance) 

1183 

1184 if diff < self._SECS_PER_MINUTE: 

1185 seconds = sign * delta_second 

1186 return locale.describe( 

1187 "seconds", seconds, only_distance=only_distance 

1188 ) 

1189 

1190 elif diff < self._SECS_PER_MINUTE * 2: 

1191 return locale.describe("minute", sign, only_distance=only_distance) 

1192 elif diff < self._SECS_PER_HOUR: 

1193 minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2) 

1194 return locale.describe( 

1195 "minutes", minutes, only_distance=only_distance 

1196 ) 

1197 

1198 elif diff < self._SECS_PER_HOUR * 2: 

1199 return locale.describe("hour", sign, only_distance=only_distance) 

1200 elif diff < self._SECS_PER_DAY: 

1201 hours = sign * max(delta_second // self._SECS_PER_HOUR, 2) 

1202 return locale.describe("hours", hours, only_distance=only_distance) 

1203 elif diff < self._SECS_PER_DAY * 2: 

1204 return locale.describe("day", sign, only_distance=only_distance) 

1205 elif diff < self._SECS_PER_WEEK: 

1206 days = sign * max(delta_second // self._SECS_PER_DAY, 2) 

1207 return locale.describe("days", days, only_distance=only_distance) 

1208 

1209 elif diff < self._SECS_PER_WEEK * 2: 

1210 return locale.describe("week", sign, only_distance=only_distance) 

1211 elif diff < self._SECS_PER_MONTH: 

1212 weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2) 

1213 return locale.describe("weeks", weeks, only_distance=only_distance) 

1214 

1215 elif diff < self._SECS_PER_MONTH * 2: 

1216 return locale.describe("month", sign, only_distance=only_distance) 

1217 elif diff < self._SECS_PER_YEAR: 

1218 # TODO revisit for humanization during leap years 

1219 self_months = self._datetime.year * 12 + self._datetime.month 

1220 other_months = dt.year * 12 + dt.month 

1221 

1222 months = sign * max(abs(other_months - self_months), 2) 

1223 

1224 return locale.describe( 

1225 "months", months, only_distance=only_distance 

1226 ) 

1227 

1228 elif diff < self._SECS_PER_YEAR * 2: 

1229 return locale.describe("year", sign, only_distance=only_distance) 

1230 else: 

1231 years = sign * max(delta_second // self._SECS_PER_YEAR, 2) 

1232 return locale.describe("years", years, only_distance=only_distance) 

1233 

1234 elif isinstance(granularity, str): 

1235 granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] 

1236 

1237 if granularity == "second": 

1238 delta = sign * float(delta_second) 

1239 if abs(delta) < 2: 

1240 return locale.describe("now", only_distance=only_distance) 

1241 elif granularity == "minute": 

1242 delta = sign * delta_second / self._SECS_PER_MINUTE 

1243 elif granularity == "hour": 

1244 delta = sign * delta_second / self._SECS_PER_HOUR 

1245 elif granularity == "day": 

1246 delta = sign * delta_second / self._SECS_PER_DAY 

1247 elif granularity == "week": 

1248 delta = sign * delta_second / self._SECS_PER_WEEK 

1249 elif granularity == "month": 

1250 delta = sign * delta_second / self._SECS_PER_MONTH 

1251 elif granularity == "quarter": 

1252 delta = sign * delta_second / self._SECS_PER_QUARTER 

1253 elif granularity == "year": 

1254 delta = sign * delta_second / self._SECS_PER_YEAR 

1255 else: 

1256 raise ValueError( 

1257 "Invalid level of granularity. " 

1258 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." 

1259 ) 

1260 

1261 if trunc(abs(delta)) != 1: 

1262 granularity += "s" # type: ignore[assignment] 

1263 return locale.describe(granularity, delta, only_distance=only_distance) 

1264 

1265 else: 

1266 if not granularity: 

1267 raise ValueError( 

1268 "Empty granularity list provided. " 

1269 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'." 

1270 ) 

1271 

1272 timeframes: List[Tuple[TimeFrameLiteral, float]] = [] 

1273 

1274 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: 

1275 if _frame in granularity: 

1276 value = sign * _delta / self._SECS_MAP[_frame] 

1277 _delta %= self._SECS_MAP[_frame] 

1278 if trunc(abs(value)) != 1: 

1279 timeframes.append( 

1280 (cast(TimeFrameLiteral, _frame + "s"), value) 

1281 ) 

1282 else: 

1283 timeframes.append((_frame, value)) 

1284 return _delta 

1285 

1286 delta = float(delta_second) 

1287 frames: Tuple[TimeFrameLiteral, ...] = ( 

1288 "year", 

1289 "quarter", 

1290 "month", 

1291 "week", 

1292 "day", 

1293 "hour", 

1294 "minute", 

1295 "second", 

1296 ) 

1297 for frame in frames: 

1298 delta = gather_timeframes(delta, frame) 

1299 

1300 if len(timeframes) < len(granularity): 

1301 raise ValueError( 

1302 "Invalid level of granularity. " 

1303 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." 

1304 ) 

1305 

1306 return locale.describe_multi(timeframes, only_distance=only_distance) 

1307 

1308 except KeyError as e: 

1309 raise ValueError( 

1310 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. " 

1311 "Please consider making a contribution to this locale." 

1312 ) 

1313 

1314 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": 

1315 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents 

1316 the time difference relative to the attributes of the 

1317 :class:`Arrow <arrow.arrow.Arrow>` object. 

1318 

1319 :param timestring: a ``str`` representing a humanized relative time. 

1320 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. 

1321 

1322 Usage:: 

1323 

1324 >>> arw = arrow.utcnow() 

1325 >>> arw 

1326 <Arrow [2021-04-20T22:27:34.787885+00:00]> 

1327 >>> earlier = arw.dehumanize("2 days ago") 

1328 >>> earlier 

1329 <Arrow [2021-04-18T22:27:34.787885+00:00]> 

1330 

1331 >>> arw = arrow.utcnow() 

1332 >>> arw 

1333 <Arrow [2021-04-20T22:27:34.787885+00:00]> 

1334 >>> later = arw.dehumanize("in a month") 

1335 >>> later 

1336 <Arrow [2021-05-18T22:27:34.787885+00:00]> 

1337 

1338 """ 

1339 

1340 # Create a locale object based off given local 

1341 locale_obj = locales.get_locale(locale) 

1342 

1343 # Check to see if locale is supported 

1344 normalized_locale_name = locale.lower().replace("_", "-") 

1345 

1346 if normalized_locale_name not in DEHUMANIZE_LOCALES: 

1347 raise ValueError( 

1348 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale." 

1349 ) 

1350 

1351 current_time = self.fromdatetime(self._datetime) 

1352 

1353 # Create an object containing the relative time info 

1354 time_object_info = dict.fromkeys( 

1355 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 

1356 ) 

1357 

1358 # Create an object representing if unit has been seen 

1359 unit_visited = dict.fromkeys( 

1360 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"], 

1361 False, 

1362 ) 

1363 

1364 # Create a regex pattern object for numbers 

1365 num_pattern = re.compile(r"\d+") 

1366 

1367 # Search input string for each time unit within locale 

1368 for unit, unit_object in locale_obj.timeframes.items(): 

1369 # Need to check the type of unit_object to create the correct dictionary 

1370 if isinstance(unit_object, Mapping): 

1371 strings_to_search = unit_object 

1372 else: 

1373 strings_to_search = {unit: str(unit_object)} 

1374 

1375 # Search for any matches that exist for that locale's unit. 

1376 # Needs to cycle all through strings as some locales have strings that 

1377 # could overlap in a regex match, since input validation isn't being performed. 

1378 for time_delta, time_string in strings_to_search.items(): 

1379 # Replace {0} with regex \d representing digits 

1380 search_string = str(time_string) 

1381 search_string = search_string.format(r"\d+") 

1382 

1383 # Create search pattern and find within string 

1384 pattern = re.compile(rf"(^|\b|\d){search_string}") 

1385 match = pattern.search(input_string) 

1386 

1387 # If there is no match continue to next iteration 

1388 if not match: 

1389 continue 

1390 

1391 match_string = match.group() 

1392 num_match = num_pattern.search(match_string) 

1393 

1394 # If no number matches 

1395 # Need for absolute value as some locales have signs included in their objects 

1396 if not num_match: 

1397 change_value = ( 

1398 1 if not time_delta.isnumeric() else abs(int(time_delta)) 

1399 ) 

1400 else: 

1401 change_value = int(num_match.group()) 

1402 

1403 # No time to update if now is the unit 

1404 if unit == "now": 

1405 unit_visited[unit] = True 

1406 continue 

1407 

1408 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds) 

1409 time_unit_to_change = str(unit) 

1410 time_unit_to_change += ( 

1411 "s" if (str(time_unit_to_change)[-1] != "s") else "" 

1412 ) 

1413 time_object_info[time_unit_to_change] = change_value 

1414 unit_visited[time_unit_to_change] = True 

1415 

1416 # Assert error if string does not modify any units 

1417 if not any([True for k, v in unit_visited.items() if v]): 

1418 raise ValueError( 

1419 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. " 

1420 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." 

1421 ) 

1422 

1423 # Sign logic 

1424 future_string = locale_obj.future 

1425 future_string = future_string.format(".*") 

1426 future_pattern = re.compile(rf"^{future_string}$") 

1427 future_pattern_match = future_pattern.findall(input_string) 

1428 

1429 past_string = locale_obj.past 

1430 past_string = past_string.format(".*") 

1431 past_pattern = re.compile(rf"^{past_string}$") 

1432 past_pattern_match = past_pattern.findall(input_string) 

1433 

1434 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit 

1435 # was visited before raising a ValueError 

1436 if past_pattern_match: 

1437 sign_val = -1 

1438 elif future_pattern_match: 

1439 sign_val = 1 

1440 elif unit_visited["now"]: 

1441 sign_val = 0 

1442 else: 

1443 raise ValueError( 

1444 "Invalid input String. String does not contain any relative time information. " 

1445 "String should either represent a time in the future or a time in the past. " 

1446 "Ex: 'in 5 seconds' or '5 seconds ago'." 

1447 ) 

1448 

1449 time_changes = {k: sign_val * v for k, v in time_object_info.items()} 

1450 

1451 return current_time.shift(check_imaginary=True, **time_changes) 

1452 

1453 # query functions 

1454 

1455 def is_between( 

1456 self, 

1457 start: "Arrow", 

1458 end: "Arrow", 

1459 bounds: _BOUNDS = "()", 

1460 ) -> bool: 

1461 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between 

1462 the start and end limits. 

1463 

1464 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object. 

1465 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object. 

1466 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

1467 whether to include or exclude the start and end values in the range. '(' excludes 

1468 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

1469 If the bounds are not specified, the default bound '()' is used. 

1470 

1471 Usage:: 

1472 

1473 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10)) 

1474 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36)) 

1475 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end) 

1476 True 

1477 

1478 >>> start = arrow.get(datetime(2013, 5, 5)) 

1479 >>> end = arrow.get(datetime(2013, 5, 8)) 

1480 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]') 

1481 True 

1482 

1483 >>> start = arrow.get(datetime(2013, 5, 5)) 

1484 >>> end = arrow.get(datetime(2013, 5, 8)) 

1485 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)') 

1486 False 

1487 

1488 """ 

1489 

1490 util.validate_bounds(bounds) 

1491 

1492 if not isinstance(start, Arrow): 

1493 raise TypeError( 

1494 f"Cannot parse start date argument type of {type(start)!r}." 

1495 ) 

1496 

1497 if not isinstance(end, Arrow): 

1498 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.") 

1499 

1500 include_start = bounds[0] == "[" 

1501 include_end = bounds[1] == "]" 

1502 

1503 target_ts = self.float_timestamp 

1504 start_ts = start.float_timestamp 

1505 end_ts = end.float_timestamp 

1506 

1507 return ( 

1508 (start_ts <= target_ts <= end_ts) 

1509 and (include_start or start_ts < target_ts) 

1510 and (include_end or target_ts < end_ts) 

1511 ) 

1512 

1513 # datetime methods 

1514 

1515 def date(self) -> date: 

1516 """Returns a ``date`` object with the same year, month and day. 

1517 

1518 Usage:: 

1519 

1520 >>> arrow.utcnow().date() 

1521 datetime.date(2019, 1, 23) 

1522 

1523 """ 

1524 

1525 return self._datetime.date() 

1526 

1527 def time(self) -> dt_time: 

1528 """Returns a ``time`` object with the same hour, minute, second, microsecond. 

1529 

1530 Usage:: 

1531 

1532 >>> arrow.utcnow().time() 

1533 datetime.time(12, 15, 34, 68352) 

1534 

1535 """ 

1536 

1537 return self._datetime.time() 

1538 

1539 def timetz(self) -> dt_time: 

1540 """Returns a ``time`` object with the same hour, minute, second, microsecond and 

1541 tzinfo. 

1542 

1543 Usage:: 

1544 

1545 >>> arrow.utcnow().timetz() 

1546 datetime.time(12, 5, 18, 298893, tzinfo=tzutc()) 

1547 

1548 """ 

1549 

1550 return self._datetime.timetz() 

1551 

1552 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime: 

1553 """Returns a ``datetime`` object, converted to the specified timezone. 

1554 

1555 :param tz: a ``tzinfo`` object. 

1556 

1557 Usage:: 

1558 

1559 >>> pacific=arrow.now('US/Pacific') 

1560 >>> nyc=arrow.now('America/New_York').tzinfo 

1561 >>> pacific.astimezone(nyc) 

1562 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York')) 

1563 

1564 """ 

1565 

1566 return self._datetime.astimezone(tz) 

1567 

1568 def utcoffset(self) -> Optional[timedelta]: 

1569 """Returns a ``timedelta`` object representing the whole number of minutes difference from 

1570 UTC time. 

1571 

1572 Usage:: 

1573 

1574 >>> arrow.now('US/Pacific').utcoffset() 

1575 datetime.timedelta(-1, 57600) 

1576 

1577 """ 

1578 

1579 return self._datetime.utcoffset() 

1580 

1581 def dst(self) -> Optional[timedelta]: 

1582 """Returns the daylight savings time adjustment. 

1583 

1584 Usage:: 

1585 

1586 >>> arrow.utcnow().dst() 

1587 datetime.timedelta(0) 

1588 

1589 """ 

1590 

1591 return self._datetime.dst() 

1592 

1593 def timetuple(self) -> struct_time: 

1594 """Returns a ``time.struct_time``, in the current timezone. 

1595 

1596 Usage:: 

1597 

1598 >>> arrow.utcnow().timetuple() 

1599 time.struct_time(tm_year=2019, tm_mon=1, tm_mday=20, tm_hour=15, tm_min=17, tm_sec=8, tm_wday=6, tm_yday=20, tm_isdst=0) 

1600 

1601 """ 

1602 

1603 return self._datetime.timetuple() 

1604 

1605 def utctimetuple(self) -> struct_time: 

1606 """Returns a ``time.struct_time``, in UTC time. 

1607 

1608 Usage:: 

1609 

1610 >>> arrow.utcnow().utctimetuple() 

1611 time.struct_time(tm_year=2019, tm_mon=1, tm_mday=19, tm_hour=21, tm_min=41, tm_sec=7, tm_wday=5, tm_yday=19, tm_isdst=0) 

1612 

1613 """ 

1614 

1615 return self._datetime.utctimetuple() 

1616 

1617 def toordinal(self) -> int: 

1618 """Returns the proleptic Gregorian ordinal of the date. 

1619 

1620 Usage:: 

1621 

1622 >>> arrow.utcnow().toordinal() 

1623 737078 

1624 

1625 """ 

1626 

1627 return self._datetime.toordinal() 

1628 

1629 def weekday(self) -> int: 

1630 """Returns the day of the week as an integer (0-6). 

1631 

1632 Usage:: 

1633 

1634 >>> arrow.utcnow().weekday() 

1635 5 

1636 

1637 """ 

1638 

1639 return self._datetime.weekday() 

1640 

1641 def isoweekday(self) -> int: 

1642 """Returns the ISO day of the week as an integer (1-7). 

1643 

1644 Usage:: 

1645 

1646 >>> arrow.utcnow().isoweekday() 

1647 6 

1648 

1649 """ 

1650 

1651 return self._datetime.isoweekday() 

1652 

1653 def isocalendar(self) -> Tuple[int, int, int]: 

1654 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday). 

1655 

1656 Usage:: 

1657 

1658 >>> arrow.utcnow().isocalendar() 

1659 (2019, 3, 6) 

1660 

1661 """ 

1662 

1663 return self._datetime.isocalendar() 

1664 

1665 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: 

1666 """Returns an ISO 8601 formatted representation of the date and time. 

1667 

1668 Usage:: 

1669 

1670 >>> arrow.utcnow().isoformat() 

1671 '2019-01-19T18:30:52.442118+00:00' 

1672 

1673 """ 

1674 

1675 return self._datetime.isoformat(sep, timespec) 

1676 

1677 def ctime(self) -> str: 

1678 """Returns a ctime formatted representation of the date and time. 

1679 

1680 Usage:: 

1681 

1682 >>> arrow.utcnow().ctime() 

1683 'Sat Jan 19 18:26:50 2019' 

1684 

1685 """ 

1686 

1687 return self._datetime.ctime() 

1688 

1689 def strftime(self, format: str) -> str: 

1690 """Formats in the style of ``datetime.strftime``. 

1691 

1692 :param format: the format string. 

1693 

1694 Usage:: 

1695 

1696 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S') 

1697 '23-01-2019 12:28:17' 

1698 

1699 """ 

1700 

1701 return self._datetime.strftime(format) 

1702 

1703 def for_json(self) -> str: 

1704 """Serializes for the ``for_json`` protocol of simplejson. 

1705 

1706 Usage:: 

1707 

1708 >>> arrow.utcnow().for_json() 

1709 '2019-01-19T18:25:36.760079+00:00' 

1710 

1711 """ 

1712 

1713 return self.isoformat() 

1714 

1715 # math 

1716 

1717 def __add__(self, other: Any) -> "Arrow": 

1718 if isinstance(other, (timedelta, relativedelta)): 

1719 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) 

1720 

1721 return NotImplemented 

1722 

1723 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow": 

1724 return self.__add__(other) 

1725 

1726 @overload 

1727 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow": 

1728 pass # pragma: no cover 

1729 

1730 @overload 

1731 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta: 

1732 pass # pragma: no cover 

1733 

1734 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: 

1735 if isinstance(other, (timedelta, relativedelta)): 

1736 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) 

1737 

1738 elif isinstance(other, dt_datetime): 

1739 return self._datetime - other 

1740 

1741 elif isinstance(other, Arrow): 

1742 return self._datetime - other._datetime 

1743 

1744 return NotImplemented 

1745 

1746 def __rsub__(self, other: Any) -> timedelta: 

1747 if isinstance(other, dt_datetime): 

1748 return other - self._datetime 

1749 

1750 return NotImplemented 

1751 

1752 # comparisons 

1753 

1754 def __eq__(self, other: Any) -> bool: 

1755 if not isinstance(other, (Arrow, dt_datetime)): 

1756 return False 

1757 

1758 return self._datetime == self._get_datetime(other) 

1759 

1760 def __ne__(self, other: Any) -> bool: 

1761 if not isinstance(other, (Arrow, dt_datetime)): 

1762 return True 

1763 

1764 return not self.__eq__(other) 

1765 

1766 def __gt__(self, other: Any) -> bool: 

1767 if not isinstance(other, (Arrow, dt_datetime)): 

1768 return NotImplemented 

1769 

1770 return self._datetime > self._get_datetime(other) 

1771 

1772 def __ge__(self, other: Any) -> bool: 

1773 if not isinstance(other, (Arrow, dt_datetime)): 

1774 return NotImplemented 

1775 

1776 return self._datetime >= self._get_datetime(other) 

1777 

1778 def __lt__(self, other: Any) -> bool: 

1779 if not isinstance(other, (Arrow, dt_datetime)): 

1780 return NotImplemented 

1781 

1782 return self._datetime < self._get_datetime(other) 

1783 

1784 def __le__(self, other: Any) -> bool: 

1785 if not isinstance(other, (Arrow, dt_datetime)): 

1786 return NotImplemented 

1787 

1788 return self._datetime <= self._get_datetime(other) 

1789 

1790 # internal methods 

1791 @staticmethod 

1792 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: 

1793 """Get normalized tzinfo object from various inputs.""" 

1794 if tz_expr is None: 

1795 return dateutil_tz.tzutc() 

1796 if isinstance(tz_expr, dt_tzinfo): 

1797 return tz_expr 

1798 else: 

1799 try: 

1800 return parser.TzinfoParser.parse(tz_expr) 

1801 except parser.ParserError: 

1802 raise ValueError(f"{tz_expr!r} not recognized as a timezone.") 

1803 

1804 @classmethod 

1805 def _get_datetime( 

1806 cls, expr: Union["Arrow", dt_datetime, int, float, str] 

1807 ) -> dt_datetime: 

1808 """Get datetime object from a specified expression.""" 

1809 if isinstance(expr, Arrow): 

1810 return expr.datetime 

1811 elif isinstance(expr, dt_datetime): 

1812 return expr 

1813 elif util.is_timestamp(expr): 

1814 timestamp = float(expr) 

1815 return cls.utcfromtimestamp(timestamp).datetime 

1816 else: 

1817 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.") 

1818 

1819 @classmethod 

1820 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]: 

1821 """Finds relevant timeframe and steps for use in range and span methods. 

1822 

1823 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1) 

1824 

1825 """ 

1826 if name in cls._ATTRS: 

1827 return name, f"{name}s", 1 

1828 elif name[-1] == "s" and name[:-1] in cls._ATTRS: 

1829 return name[:-1], name, 1 

1830 elif name in ["week", "weeks"]: 

1831 return "week", "weeks", 1 

1832 elif name in ["quarter", "quarters"]: 

1833 return "quarter", "months", 3 

1834 else: 

1835 supported = ", ".join( 

1836 [ 

1837 "year(s)", 

1838 "month(s)", 

1839 "day(s)", 

1840 "hour(s)", 

1841 "minute(s)", 

1842 "second(s)", 

1843 "microsecond(s)", 

1844 "week(s)", 

1845 "quarter(s)", 

1846 ] 

1847 ) 

1848 raise ValueError( 

1849 f"Range or span over frame {name} not supported. Supported frames: {supported}." 

1850 ) 

1851 

1852 @classmethod 

1853 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: 

1854 """Sets default end and limit values for range method.""" 

1855 if end is None: 

1856 if limit is None: 

1857 raise ValueError("One of 'end' or 'limit' is required.") 

1858 

1859 return cls.max, limit 

1860 

1861 else: 

1862 if limit is None: 

1863 return end, sys.maxsize 

1864 return end, limit 

1865 

1866 @staticmethod 

1867 def _is_last_day_of_month(date: "Arrow") -> bool: 

1868 """Returns a boolean indicating whether the datetime is the last day of the month.""" 

1869 return cast(int, date.day) == calendar.monthrange(date.year, date.month)[1] 

1870 

1871 

1872Arrow.min = Arrow.fromdatetime(dt_datetime.min) 

1873Arrow.max = Arrow.fromdatetime(dt_datetime.max)