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

558 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:17 +0000

1""" 

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

3replacement. 

4 

5""" 

6 

7 

8import calendar 

9import re 

10import sys 

11from datetime import date 

12from datetime import datetime as dt_datetime 

13from datetime import time as dt_time 

14from datetime import timedelta 

15from datetime import tzinfo as dt_tzinfo 

16from math import trunc 

17from time import struct_time 

18from typing import ( 

19 Any, 

20 ClassVar, 

21 Generator, 

22 Iterable, 

23 List, 

24 Mapping, 

25 Optional, 

26 Tuple, 

27 Union, 

28 cast, 

29 overload, 

30) 

31 

32from dateutil import tz as dateutil_tz 

33from dateutil.relativedelta import relativedelta 

34 

35from arrow import formatter, locales, parser, util 

36from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES 

37from arrow.locales import TimeFrameLiteral 

38 

39if sys.version_info < (3, 8): # pragma: no cover 

40 from typing_extensions import Final, Literal 

41else: 

42 from typing import Final, Literal # pragma: no cover 

43 

44 

45TZ_EXPR = Union[dt_tzinfo, str] 

46 

47_T_FRAMES = Literal[ 

48 "year", 

49 "years", 

50 "month", 

51 "months", 

52 "day", 

53 "days", 

54 "hour", 

55 "hours", 

56 "minute", 

57 "minutes", 

58 "second", 

59 "seconds", 

60 "microsecond", 

61 "microseconds", 

62 "week", 

63 "weeks", 

64 "quarter", 

65 "quarters", 

66] 

67 

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

69 

70_GRANULARITY = Literal[ 

71 "auto", 

72 "second", 

73 "minute", 

74 "hour", 

75 "day", 

76 "week", 

77 "month", 

78 "quarter", 

79 "year", 

80] 

81 

82 

83class Arrow: 

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

85 

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

87 additional functionality. 

88 

89 :param year: the calendar year. 

90 :param month: the calendar month. 

91 :param day: the calendar day. 

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

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

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

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

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

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

98 

99 .. _tz-expr: 

100 

101 Recognized timezone expressions: 

102 

103 - A ``tzinfo`` object. 

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

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

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

107 

108 Usage:: 

109 

110 >>> import arrow 

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

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

113 

114 """ 

115 

116 resolution: ClassVar[timedelta] = dt_datetime.resolution 

117 min: ClassVar["Arrow"] 

118 max: ClassVar["Arrow"] 

119 

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

121 "year", 

122 "month", 

123 "day", 

124 "hour", 

125 "minute", 

126 "second", 

127 "microsecond", 

128 ] 

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

130 _MONTHS_PER_QUARTER: Final[int] = 3 

131 _SECS_PER_MINUTE: Final[int] = 60 

132 _SECS_PER_HOUR: Final[int] = 60 * 60 

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

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

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

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

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

138 

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

140 "second": 1.0, 

141 "minute": _SECS_PER_MINUTE, 

142 "hour": _SECS_PER_HOUR, 

143 "day": _SECS_PER_DAY, 

144 "week": _SECS_PER_WEEK, 

145 "month": _SECS_PER_MONTH, 

146 "quarter": _SECS_PER_QUARTER, 

147 "year": _SECS_PER_YEAR, 

148 } 

149 

150 _datetime: dt_datetime 

151 

152 def __init__( 

153 self, 

154 year: int, 

155 month: int, 

156 day: int, 

157 hour: int = 0, 

158 minute: int = 0, 

159 second: int = 0, 

160 microsecond: int = 0, 

161 tzinfo: Optional[TZ_EXPR] = None, 

162 **kwargs: Any, 

163 ) -> None: 

164 if tzinfo is None: 

165 tzinfo = dateutil_tz.tzutc() 

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

167 elif ( 

168 isinstance(tzinfo, dt_tzinfo) 

169 and hasattr(tzinfo, "localize") 

170 and hasattr(tzinfo, "zone") 

171 and tzinfo.zone # type: ignore[attr-defined] 

172 ): 

173 tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined] 

174 elif isinstance(tzinfo, str): 

175 tzinfo = parser.TzinfoParser.parse(tzinfo) 

176 

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

178 

179 self._datetime = dt_datetime( 

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

181 ) 

182 

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

184 

185 @classmethod 

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

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

188 timezone. 

189 

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

191 

192 Usage:: 

193 

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

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

196 

197 """ 

198 

199 if tzinfo is None: 

200 tzinfo = dateutil_tz.tzlocal() 

201 

202 dt = dt_datetime.now(tzinfo) 

203 

204 return cls( 

205 dt.year, 

206 dt.month, 

207 dt.day, 

208 dt.hour, 

209 dt.minute, 

210 dt.second, 

211 dt.microsecond, 

212 dt.tzinfo, 

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

214 ) 

215 

216 @classmethod 

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

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

219 time. 

220 

221 Usage:: 

222 

223 >>> arrow.utcnow() 

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

225 

226 """ 

227 

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

229 

230 return cls( 

231 dt.year, 

232 dt.month, 

233 dt.day, 

234 dt.hour, 

235 dt.minute, 

236 dt.second, 

237 dt.microsecond, 

238 dt.tzinfo, 

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

240 ) 

241 

242 @classmethod 

243 def fromtimestamp( 

244 cls, 

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

246 tzinfo: Optional[TZ_EXPR] = None, 

247 ) -> "Arrow": 

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

249 the given timezone. 

250 

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

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

253 

254 """ 

255 

256 if tzinfo is None: 

257 tzinfo = dateutil_tz.tzlocal() 

258 elif isinstance(tzinfo, str): 

259 tzinfo = parser.TzinfoParser.parse(tzinfo) 

260 

261 if not util.is_timestamp(timestamp): 

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

263 

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

265 dt = dt_datetime.fromtimestamp(timestamp, tzinfo) 

266 

267 return cls( 

268 dt.year, 

269 dt.month, 

270 dt.day, 

271 dt.hour, 

272 dt.minute, 

273 dt.second, 

274 dt.microsecond, 

275 dt.tzinfo, 

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

277 ) 

278 

279 @classmethod 

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

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

282 

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

284 

285 """ 

286 

287 if not util.is_timestamp(timestamp): 

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

289 

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

291 dt = dt_datetime.utcfromtimestamp(timestamp) 

292 

293 return cls( 

294 dt.year, 

295 dt.month, 

296 dt.day, 

297 dt.hour, 

298 dt.minute, 

299 dt.second, 

300 dt.microsecond, 

301 dateutil_tz.tzutc(), 

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

303 ) 

304 

305 @classmethod 

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

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

308 optional replacement timezone. 

309 

310 :param dt: the ``datetime`` 

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

312 timezone, or UTC if naive. 

313 

314 Usage:: 

315 

316 >>> dt 

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

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

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

320 

321 """ 

322 

323 if tzinfo is None: 

324 if dt.tzinfo is None: 

325 tzinfo = dateutil_tz.tzutc() 

326 else: 

327 tzinfo = dt.tzinfo 

328 

329 return cls( 

330 dt.year, 

331 dt.month, 

332 dt.day, 

333 dt.hour, 

334 dt.minute, 

335 dt.second, 

336 dt.microsecond, 

337 tzinfo, 

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

339 ) 

340 

341 @classmethod 

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

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

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

345 

346 :param date: the ``date`` 

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

348 

349 """ 

350 

351 if tzinfo is None: 

352 tzinfo = dateutil_tz.tzutc() 

353 

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

355 

356 @classmethod 

357 def strptime( 

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

359 ) -> "Arrow": 

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

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

362 

363 :param date_str: the date string. 

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

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

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

367 

368 Usage:: 

369 

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

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

372 

373 """ 

374 

375 dt = dt_datetime.strptime(date_str, fmt) 

376 if tzinfo is None: 

377 tzinfo = dt.tzinfo 

378 

379 return cls( 

380 dt.year, 

381 dt.month, 

382 dt.day, 

383 dt.hour, 

384 dt.minute, 

385 dt.second, 

386 dt.microsecond, 

387 tzinfo, 

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

389 ) 

390 

391 @classmethod 

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

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

394 to the Gregorian Ordinal. 

395 

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

397 

398 Usage:: 

399 

400 >>> arrow.fromordinal(737741) 

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

402 

403 """ 

404 

405 util.validate_ordinal(ordinal) 

406 dt = dt_datetime.fromordinal(ordinal) 

407 return cls( 

408 dt.year, 

409 dt.month, 

410 dt.day, 

411 dt.hour, 

412 dt.minute, 

413 dt.second, 

414 dt.microsecond, 

415 dt.tzinfo, 

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

417 ) 

418 

419 # factories: ranges and spans 

420 

421 @classmethod 

422 def range( 

423 cls, 

424 frame: _T_FRAMES, 

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

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

427 tz: Optional[TZ_EXPR] = None, 

428 limit: Optional[int] = None, 

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

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

431 points in time between two inputs. 

432 

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

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

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

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

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

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

439 

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

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

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

443 

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

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

446 same timezone and no ``tz``. 

447 

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

449 

450 Recognized datetime expressions: 

451 

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

453 - A ``datetime`` object. 

454 

455 Usage:: 

456 

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

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

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

460 ... print(repr(r)) 

461 ... 

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

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

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

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

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

467 

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

469 

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

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

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

473 ... print(repr(r)) 

474 ... 

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

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

477 

478 """ 

479 

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

481 

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

483 

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

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

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

487 

488 current = cls.fromdatetime(start) 

489 original_day = start.day 

490 day_is_clipped = False 

491 i = 0 

492 

493 while current <= end and i < limit: 

494 i += 1 

495 yield current 

496 

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

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

499 **{frame_relative: relative_steps} 

500 ) 

501 

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

503 day_is_clipped = True 

504 

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

506 current = current.replace(day=original_day) 

507 

508 def span( 

509 self, 

510 frame: _T_FRAMES, 

511 count: int = 1, 

512 bounds: _BOUNDS = "[)", 

513 exact: bool = False, 

514 week_start: int = 1, 

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

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

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

518 

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

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

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

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

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

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

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

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

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

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

529 Monday is 1 and Sunday is 7. 

530 

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

532 

533 Usage:: 

534 

535 >>> arrow.utcnow() 

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

537 

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

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

540 

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

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

543 

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

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

546 

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

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

549 

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

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

552 

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

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

555 

556 """ 

557 if not 1 <= week_start <= 7: 

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

559 

560 util.validate_bounds(bounds) 

561 

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

563 

564 if frame_absolute == "week": 

565 attr = "day" 

566 elif frame_absolute == "quarter": 

567 attr = "month" 

568 else: 

569 attr = frame_absolute 

570 

571 floor = self 

572 if not exact: 

573 index = self._ATTRS.index(attr) 

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

575 

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

577 

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

579 values.append(1) 

580 

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

582 

583 if frame_absolute == "week": 

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

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

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

587 elif frame_absolute == "quarter": 

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

589 

590 ceil = floor.shift(**{frame_relative: count * relative_steps}) 

591 

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

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

594 

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

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

597 

598 return floor, ceil 

599 

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

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

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

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

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

605 

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

607 

608 Usage:: 

609 

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

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

612 

613 """ 

614 

615 return self.span(frame)[0] 

616 

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

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

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

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

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

622 

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

624 

625 Usage:: 

626 

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

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

629 

630 """ 

631 

632 return self.span(frame)[1] 

633 

634 @classmethod 

635 def span_range( 

636 cls, 

637 frame: _T_FRAMES, 

638 start: dt_datetime, 

639 end: dt_datetime, 

640 tz: Optional[TZ_EXPR] = None, 

641 limit: Optional[int] = None, 

642 bounds: _BOUNDS = "[)", 

643 exact: bool = False, 

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

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

646 representing a series of timespans between two inputs. 

647 

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

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

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

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

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

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

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

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

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

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

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

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

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

661 

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

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

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

665 

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

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

668 same timezone and no ``tz``. 

669 

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

671 

672 Recognized datetime expressions: 

673 

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

675 - A ``datetime`` object. 

676 

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

678 iterator of timespans. 

679 

680 Usage: 

681 

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

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

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

685 ... print(r) 

686 ... 

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

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

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

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

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

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

693 

694 """ 

695 

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

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

698 end = cls.fromdatetime(end, tzinfo) 

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

700 if not exact: 

701 for r in _range: 

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

703 

704 for r in _range: 

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

706 if ceil > end: 

707 ceil = end 

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

709 ceil += relativedelta(microseconds=-1) 

710 if floor == end: 

711 break 

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

713 break 

714 yield floor, ceil 

715 

716 @classmethod 

717 def interval( 

718 cls, 

719 frame: _T_FRAMES, 

720 start: dt_datetime, 

721 end: dt_datetime, 

722 interval: int = 1, 

723 tz: Optional[TZ_EXPR] = None, 

724 bounds: _BOUNDS = "[)", 

725 exact: bool = False, 

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

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

728 representing a series of intervals between two inputs. 

729 

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

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

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

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

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

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

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

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

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

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

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

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

742 

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

744 

745 Recognized datetime expressions: 

746 

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

748 - A ``datetime`` object. 

749 

750 Recognized timezone expressions: 

751 

752 - A ``tzinfo`` object. 

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

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

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

756 

757 Usage: 

758 

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

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

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

762 ... print(r) 

763 ... 

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

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

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

767 """ 

768 if interval < 1: 

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

770 

771 spanRange = iter( 

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

773 ) 

774 while True: 

775 try: 

776 intvlStart, intvlEnd = next(spanRange) 

777 for _ in range(interval - 1): 

778 try: 

779 _, intvlEnd = next(spanRange) 

780 except StopIteration: 

781 continue 

782 yield intvlStart, intvlEnd 

783 except StopIteration: 

784 return 

785 

786 # representations 

787 

788 def __repr__(self) -> str: 

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

790 

791 def __str__(self) -> str: 

792 return self._datetime.isoformat() 

793 

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

795 

796 if len(formatstr) > 0: 

797 return self.format(formatstr) 

798 

799 return str(self) 

800 

801 def __hash__(self) -> int: 

802 return self._datetime.__hash__() 

803 

804 # attributes and properties 

805 

806 def __getattr__(self, name: str) -> int: 

807 

808 if name == "week": 

809 return self.isocalendar()[1] 

810 

811 if name == "quarter": 

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

813 

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

815 value: Optional[int] = getattr(self._datetime, name, None) 

816 

817 if value is not None: 

818 return value 

819 

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

821 

822 @property 

823 def tzinfo(self) -> dt_tzinfo: 

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

825 

826 Usage:: 

827 

828 >>> arw=arrow.utcnow() 

829 >>> arw.tzinfo 

830 tzutc() 

831 

832 """ 

833 

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

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

836 

837 @property 

838 def datetime(self) -> dt_datetime: 

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

840 

841 Usage:: 

842 

843 >>> arw=arrow.utcnow() 

844 >>> arw.datetime 

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

846 

847 """ 

848 

849 return self._datetime 

850 

851 @property 

852 def naive(self) -> dt_datetime: 

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

854 object. 

855 

856 Usage:: 

857 

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

859 >>> nairobi 

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

861 >>> nairobi.naive 

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

863 

864 """ 

865 

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

867 

868 def timestamp(self) -> float: 

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

870 UTC time. 

871 

872 Usage:: 

873 

874 >>> arrow.utcnow().timestamp() 

875 1616882340.256501 

876 

877 """ 

878 

879 return self._datetime.timestamp() 

880 

881 @property 

882 def int_timestamp(self) -> int: 

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

884 UTC time. 

885 

886 Usage:: 

887 

888 >>> arrow.utcnow().int_timestamp 

889 1548260567 

890 

891 """ 

892 

893 return int(self.timestamp()) 

894 

895 @property 

896 def float_timestamp(self) -> float: 

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

898 object, in UTC time. 

899 

900 Usage:: 

901 

902 >>> arrow.utcnow().float_timestamp 

903 1548260516.830896 

904 

905 """ 

906 

907 return self.timestamp() 

908 

909 @property 

910 def fold(self) -> int: 

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

912 

913 return self._datetime.fold 

914 

915 @property 

916 def ambiguous(self) -> bool: 

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

918 timezone. 

919 

920 """ 

921 

922 return dateutil_tz.datetime_ambiguous(self._datetime) 

923 

924 @property 

925 def imaginary(self) -> bool: 

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

927 

928 return not dateutil_tz.datetime_exists(self._datetime) 

929 

930 # mutation and duplication. 

931 

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

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

934 

935 Usage: 

936 

937 >>> arw = arrow.utcnow() 

938 >>> cloned = arw.clone() 

939 

940 """ 

941 

942 return self.fromdatetime(self._datetime) 

943 

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

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

946 according to inputs. 

947 

948 Use property names to set their value absolutely:: 

949 

950 >>> import arrow 

951 >>> arw = arrow.utcnow() 

952 >>> arw 

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

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

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

956 

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

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

959 

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

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

962 

963 """ 

964 

965 absolute_kwargs = {} 

966 

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

968 

969 if key in self._ATTRS: 

970 absolute_kwargs[key] = value 

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

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

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

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

975 

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

977 

978 tzinfo = kwargs.get("tzinfo") 

979 

980 if tzinfo is not None: 

981 tzinfo = self._get_tzinfo(tzinfo) 

982 current = current.replace(tzinfo=tzinfo) 

983 

984 fold = kwargs.get("fold") 

985 

986 if fold is not None: 

987 current = current.replace(fold=fold) 

988 

989 return self.fromdatetime(current) 

990 

991 def shift(self, **kwargs: Any) -> "Arrow": 

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

993 according to inputs. 

994 

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

996 

997 >>> import arrow 

998 >>> arw = arrow.utcnow() 

999 >>> arw 

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

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

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

1003 

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

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

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

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

1008 

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

1010 

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

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

1013 

1014 While asking for a Monday: 

1015 

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

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

1018 

1019 """ 

1020 

1021 relative_kwargs = {} 

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

1023 

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

1025 

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

1027 relative_kwargs[key] = value 

1028 else: 

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

1030 raise ValueError( 

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

1032 ) 

1033 

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

1035 relative_kwargs.setdefault("months", 0) 

1036 relative_kwargs["months"] += ( 

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

1038 ) 

1039 

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

1041 

1042 if not dateutil_tz.datetime_exists(current): 

1043 current = dateutil_tz.resolve_imaginary(current) 

1044 

1045 return self.fromdatetime(current) 

1046 

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

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

1049 to the target timezone. 

1050 

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

1052 

1053 Usage:: 

1054 

1055 >>> utc = arrow.utcnow() 

1056 >>> utc 

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

1058 

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

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

1061 

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

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

1064 

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

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

1067 

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

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

1070 

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

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

1073 

1074 """ 

1075 

1076 if not isinstance(tz, dt_tzinfo): 

1077 tz = parser.TzinfoParser.parse(tz) 

1078 

1079 dt = self._datetime.astimezone(tz) 

1080 

1081 return self.__class__( 

1082 dt.year, 

1083 dt.month, 

1084 dt.day, 

1085 dt.hour, 

1086 dt.minute, 

1087 dt.second, 

1088 dt.microsecond, 

1089 dt.tzinfo, 

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

1091 ) 

1092 

1093 # string output and formatting 

1094 

1095 def format( 

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

1097 ) -> str: 

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

1099 formatted according to the provided format string. 

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.utcnow().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 

1267 if not granularity: 

1268 raise ValueError( 

1269 "Empty granularity list provided. " 

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

1271 ) 

1272 

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

1274 

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

1276 if _frame in granularity: 

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

1278 _delta %= self._SECS_MAP[_frame] 

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

1280 timeframes.append( 

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

1282 ) 

1283 else: 

1284 timeframes.append((_frame, value)) 

1285 return _delta 

1286 

1287 delta = float(delta_second) 

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

1289 "year", 

1290 "quarter", 

1291 "month", 

1292 "week", 

1293 "day", 

1294 "hour", 

1295 "minute", 

1296 "second", 

1297 ) 

1298 for frame in frames: 

1299 delta = gather_timeframes(delta, frame) 

1300 

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

1302 raise ValueError( 

1303 "Invalid level of granularity. " 

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

1305 ) 

1306 

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

1308 

1309 except KeyError as e: 

1310 raise ValueError( 

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

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

1313 ) 

1314 

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

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

1317 the time difference relative to the attributes of the 

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

1319 

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

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

1322 

1323 Usage:: 

1324 

1325 >>> arw = arrow.utcnow() 

1326 >>> arw 

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

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

1329 >>> earlier 

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

1331 

1332 >>> arw = arrow.utcnow() 

1333 >>> arw 

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

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

1336 >>> later 

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

1338 

1339 """ 

1340 

1341 # Create a locale object based off given local 

1342 locale_obj = locales.get_locale(locale) 

1343 

1344 # Check to see if locale is supported 

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

1346 

1347 if normalized_locale_name not in DEHUMANIZE_LOCALES: 

1348 raise ValueError( 

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

1350 ) 

1351 

1352 current_time = self.fromdatetime(self._datetime) 

1353 

1354 # Create an object containing the relative time info 

1355 time_object_info = dict.fromkeys( 

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

1357 ) 

1358 

1359 # Create an object representing if unit has been seen 

1360 unit_visited = dict.fromkeys( 

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

1362 False, 

1363 ) 

1364 

1365 # Create a regex pattern object for numbers 

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

1367 

1368 # Search input string for each time unit within locale 

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

1370 

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

1372 if isinstance(unit_object, Mapping): 

1373 strings_to_search = unit_object 

1374 else: 

1375 strings_to_search = {unit: str(unit_object)} 

1376 

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

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

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

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

1381 

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

1383 search_string = str(time_string) 

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

1385 

1386 # Create search pattern and find within string 

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

1388 match = pattern.search(input_string) 

1389 

1390 # If there is no match continue to next iteration 

1391 if not match: 

1392 continue 

1393 

1394 match_string = match.group() 

1395 num_match = num_pattern.search(match_string) 

1396 

1397 # If no number matches 

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

1399 if not num_match: 

1400 change_value = ( 

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

1402 ) 

1403 else: 

1404 change_value = int(num_match.group()) 

1405 

1406 # No time to update if now is the unit 

1407 if unit == "now": 

1408 unit_visited[unit] = True 

1409 continue 

1410 

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

1412 time_unit_to_change = str(unit) 

1413 time_unit_to_change += ( 

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

1415 ) 

1416 time_object_info[time_unit_to_change] = change_value 

1417 unit_visited[time_unit_to_change] = True 

1418 

1419 # Assert error if string does not modify any units 

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

1421 raise ValueError( 

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

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

1424 ) 

1425 

1426 # Sign logic 

1427 future_string = locale_obj.future 

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

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

1430 future_pattern_match = future_pattern.findall(input_string) 

1431 

1432 past_string = locale_obj.past 

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

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

1435 past_pattern_match = past_pattern.findall(input_string) 

1436 

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

1438 # was visited before raising a ValueError 

1439 if past_pattern_match: 

1440 sign_val = -1 

1441 elif future_pattern_match: 

1442 sign_val = 1 

1443 elif unit_visited["now"]: 

1444 sign_val = 0 

1445 else: 

1446 raise ValueError( 

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

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

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

1450 ) 

1451 

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

1453 

1454 return current_time.shift(**time_changes) 

1455 

1456 # query functions 

1457 

1458 def is_between( 

1459 self, 

1460 start: "Arrow", 

1461 end: "Arrow", 

1462 bounds: _BOUNDS = "()", 

1463 ) -> bool: 

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

1465 the start and end limits. 

1466 

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

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

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

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

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

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

1473 

1474 Usage:: 

1475 

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

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

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

1479 True 

1480 

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

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

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

1484 True 

1485 

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

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

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

1489 False 

1490 

1491 """ 

1492 

1493 util.validate_bounds(bounds) 

1494 

1495 if not isinstance(start, Arrow): 

1496 raise TypeError( 

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

1498 ) 

1499 

1500 if not isinstance(end, Arrow): 

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

1502 

1503 include_start = bounds[0] == "[" 

1504 include_end = bounds[1] == "]" 

1505 

1506 target_ts = self.float_timestamp 

1507 start_ts = start.float_timestamp 

1508 end_ts = end.float_timestamp 

1509 

1510 return ( 

1511 (start_ts <= target_ts <= end_ts) 

1512 and (include_start or start_ts < target_ts) 

1513 and (include_end or target_ts < end_ts) 

1514 ) 

1515 

1516 # datetime methods 

1517 

1518 def date(self) -> date: 

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

1520 

1521 Usage:: 

1522 

1523 >>> arrow.utcnow().date() 

1524 datetime.date(2019, 1, 23) 

1525 

1526 """ 

1527 

1528 return self._datetime.date() 

1529 

1530 def time(self) -> dt_time: 

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

1532 

1533 Usage:: 

1534 

1535 >>> arrow.utcnow().time() 

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

1537 

1538 """ 

1539 

1540 return self._datetime.time() 

1541 

1542 def timetz(self) -> dt_time: 

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

1544 tzinfo. 

1545 

1546 Usage:: 

1547 

1548 >>> arrow.utcnow().timetz() 

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

1550 

1551 """ 

1552 

1553 return self._datetime.timetz() 

1554 

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

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

1557 

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

1559 

1560 Usage:: 

1561 

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

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

1564 >>> pacific.astimezone(nyc) 

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

1566 

1567 """ 

1568 

1569 return self._datetime.astimezone(tz) 

1570 

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

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

1573 UTC time. 

1574 

1575 Usage:: 

1576 

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

1578 datetime.timedelta(-1, 57600) 

1579 

1580 """ 

1581 

1582 return self._datetime.utcoffset() 

1583 

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

1585 """Returns the daylight savings time adjustment. 

1586 

1587 Usage:: 

1588 

1589 >>> arrow.utcnow().dst() 

1590 datetime.timedelta(0) 

1591 

1592 """ 

1593 

1594 return self._datetime.dst() 

1595 

1596 def timetuple(self) -> struct_time: 

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

1598 

1599 Usage:: 

1600 

1601 >>> arrow.utcnow().timetuple() 

1602 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) 

1603 

1604 """ 

1605 

1606 return self._datetime.timetuple() 

1607 

1608 def utctimetuple(self) -> struct_time: 

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

1610 

1611 Usage:: 

1612 

1613 >>> arrow.utcnow().utctimetuple() 

1614 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) 

1615 

1616 """ 

1617 

1618 return self._datetime.utctimetuple() 

1619 

1620 def toordinal(self) -> int: 

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

1622 

1623 Usage:: 

1624 

1625 >>> arrow.utcnow().toordinal() 

1626 737078 

1627 

1628 """ 

1629 

1630 return self._datetime.toordinal() 

1631 

1632 def weekday(self) -> int: 

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

1634 

1635 Usage:: 

1636 

1637 >>> arrow.utcnow().weekday() 

1638 5 

1639 

1640 """ 

1641 

1642 return self._datetime.weekday() 

1643 

1644 def isoweekday(self) -> int: 

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

1646 

1647 Usage:: 

1648 

1649 >>> arrow.utcnow().isoweekday() 

1650 6 

1651 

1652 """ 

1653 

1654 return self._datetime.isoweekday() 

1655 

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

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

1658 

1659 Usage:: 

1660 

1661 >>> arrow.utcnow().isocalendar() 

1662 (2019, 3, 6) 

1663 

1664 """ 

1665 

1666 return self._datetime.isocalendar() 

1667 

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

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

1670 

1671 Usage:: 

1672 

1673 >>> arrow.utcnow().isoformat() 

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

1675 

1676 """ 

1677 

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

1679 

1680 def ctime(self) -> str: 

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

1682 

1683 Usage:: 

1684 

1685 >>> arrow.utcnow().ctime() 

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

1687 

1688 """ 

1689 

1690 return self._datetime.ctime() 

1691 

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

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

1694 

1695 :param format: the format string. 

1696 

1697 Usage:: 

1698 

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

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

1701 

1702 """ 

1703 

1704 return self._datetime.strftime(format) 

1705 

1706 def for_json(self) -> str: 

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

1708 

1709 Usage:: 

1710 

1711 >>> arrow.utcnow().for_json() 

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

1713 

1714 """ 

1715 

1716 return self.isoformat() 

1717 

1718 # math 

1719 

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

1721 

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

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

1724 

1725 return NotImplemented 

1726 

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

1728 return self.__add__(other) 

1729 

1730 @overload 

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

1732 pass # pragma: no cover 

1733 

1734 @overload 

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

1736 pass # pragma: no cover 

1737 

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

1739 

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

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

1742 

1743 elif isinstance(other, dt_datetime): 

1744 return self._datetime - other 

1745 

1746 elif isinstance(other, Arrow): 

1747 return self._datetime - other._datetime 

1748 

1749 return NotImplemented 

1750 

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

1752 

1753 if isinstance(other, dt_datetime): 

1754 return other - self._datetime 

1755 

1756 return NotImplemented 

1757 

1758 # comparisons 

1759 

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

1761 

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

1763 return False 

1764 

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

1766 

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

1768 

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

1770 return True 

1771 

1772 return not self.__eq__(other) 

1773 

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

1775 

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

1777 return NotImplemented 

1778 

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

1780 

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

1782 

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

1784 return NotImplemented 

1785 

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

1787 

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

1789 

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

1791 return NotImplemented 

1792 

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

1794 

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

1796 

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

1798 return NotImplemented 

1799 

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

1801 

1802 # internal methods 

1803 @staticmethod 

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

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

1806 if tz_expr is None: 

1807 return dateutil_tz.tzutc() 

1808 if isinstance(tz_expr, dt_tzinfo): 

1809 return tz_expr 

1810 else: 

1811 try: 

1812 return parser.TzinfoParser.parse(tz_expr) 

1813 except parser.ParserError: 

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

1815 

1816 @classmethod 

1817 def _get_datetime( 

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

1819 ) -> dt_datetime: 

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

1821 if isinstance(expr, Arrow): 

1822 return expr.datetime 

1823 elif isinstance(expr, dt_datetime): 

1824 return expr 

1825 elif util.is_timestamp(expr): 

1826 timestamp = float(expr) 

1827 return cls.utcfromtimestamp(timestamp).datetime 

1828 else: 

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

1830 

1831 @classmethod 

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

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

1834 

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

1836 

1837 """ 

1838 if name in cls._ATTRS: 

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

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

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

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

1843 return "week", "weeks", 1 

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

1845 return "quarter", "months", 3 

1846 else: 

1847 supported = ", ".join( 

1848 [ 

1849 "year(s)", 

1850 "month(s)", 

1851 "day(s)", 

1852 "hour(s)", 

1853 "minute(s)", 

1854 "second(s)", 

1855 "microsecond(s)", 

1856 "week(s)", 

1857 "quarter(s)", 

1858 ] 

1859 ) 

1860 raise ValueError( 

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

1862 ) 

1863 

1864 @classmethod 

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

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

1867 if end is None: 

1868 

1869 if limit is None: 

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

1871 

1872 return cls.max, limit 

1873 

1874 else: 

1875 if limit is None: 

1876 return end, sys.maxsize 

1877 return end, limit 

1878 

1879 @staticmethod 

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

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

1882 return date.day == calendar.monthrange(date.year, date.month)[1] 

1883 

1884 

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

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