cardinal_pythonlib.datetimefunc


Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).

This file is part of cardinal_pythonlib.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


Support functions for date/time.

Note regarding durations:

  • datetime.timedelta takes parameters from microseconds to weeks; these are all exact.

  • isodate.isoduration.Duration also includes years and months, which are well defined but not constant. It is explicit that it has two basic components: {year, month} and {timedelta}. Internally, it also treats years and months as separate.

  • pendulum.Duration has the same span from microseconds to years, but it has internal assumptions (in v2.1.1+ at least) that a year is 365 days and a month is 30 days.

cardinal_pythonlib.datetimefunc.coerce_to_date(x: Any, assume_local: bool = False, to_utc: bool = False) date | None[source]

Ensure an object is a datetime.date, or coerce to one, or raise ValueError or OverflowError (as per https://dateutil.readthedocs.org/en/latest/parser.html).

See also coerce_to_pendulum_date(), noting that pendulum.Date is a subclass of datetime.date.

cardinal_pythonlib.datetimefunc.coerce_to_datetime(x: Any) datetime | None[source]

Ensure an object is a datetime.datetime, or coerce to one, or raise ValueError or OverflowError (as per https://dateutil.readthedocs.org/en/latest/parser.html).

cardinal_pythonlib.datetimefunc.coerce_to_pendulum(x: None | datetime | date | DateTime | str | Arrow, assume_local: bool = False) DateTime | None[source]

Converts something to a pendulum.DateTime.

Parameters:
  • x – Something that may be coercible to a datetime.

  • assume_local – Governs what happens if no timezone information is present in the source object. If True, assume local timezone; if False, assume UTC.

Returns:

a pendulum.DateTime, or None.

Raises:
  • pendulum.parsing.exceptions.ParserError – if a string fails to parse

  • ValueError – if no conversion possible

cardinal_pythonlib.datetimefunc.coerce_to_pendulum_date(x: None | datetime | date | DateTime | str | Arrow, assume_local: bool = False, to_utc: bool = False) Date | None[source]

Converts something to a pendulum.Date.

Parameters:
  • x – Something that may be coercible to a date.

  • assume_local – Governs what happens if no timezone information is present in the source object. If True, assume local timezone; if False, assume UTC.

  • to_utc – Should we return the date in UTC (e.g. London) (True), or the date in the timezone of the source (False)? For example, 2022-02-27T23:00-05:00 (11pm in New York) is 2022-02-28T04:00Z (4am in London). Do you want the return value to be 27 Feb (to_utc=False) or 28 Feb (to_utc=True)?

Returns:

a pendulum.Date, or None.

Raises:
  • pendulum.parsing.exceptions.ParserError – if a string fails to parse

  • ValueError – if no conversion possible

cardinal_pythonlib.datetimefunc.convert_datetime_to_local(dt: None | datetime | date | DateTime | str | Arrow) DateTime[source]

Convert date/time with timezone to local timezone.

cardinal_pythonlib.datetimefunc.convert_datetime_to_utc(dt: None | datetime | date | DateTime | str | Arrow) DateTime[source]

Convert date/time with timezone to UTC (with UTC timezone).

cardinal_pythonlib.datetimefunc.duration_from_iso(iso_duration: str) Duration[source]

Converts an ISO-8601 format duration into a pendulum.Duration.

:raises - isodate.isoerror.ISO8601Error for bad input: :raises - ValueError if the input had non-integer year or month values:

  • The ISO-8601 duration format is P[n]Y[n]M[n]DT[n]H[n]M[n]S; see https://en.wikipedia.org/wiki/ISO_8601#Durations.

    • P = period, or duration designator, which comes first

      • [n]Y = number of years

      • [n]M = number of months

      • [n]W = number of weeks

      • [n]D = number of days

    • T = time designator (precedes the time component)

      • [n]H = number of hours

      • [n]M = number of minutes

      • [n]S = number of seconds

  • pendulum.Duration.min and pendulum.Duration.max values are Duration(weeks=-142857142, days=-5) and Duration(weeks=142857142, days=6) respectively.

  • isodate supports negative durations of the format -P<something>, such as -PT5S for “minus 5 seconds”, but not e.g. PT-5S.

  • I’m not clear if ISO-8601 itself supports negative durations. This suggests not: https://github.com/moment/moment/issues/2408. But lots of implementations (including to some limited extent isodate) do support this concept.

cardinal_pythonlib.datetimefunc.duration_to_iso(d: Duration, permit_years_months: bool = True, minus_sign_at_front: bool = True) str[source]

Converts a pendulum.Duration into an ISO-8601 formatted string.

Parameters:
  • d – the duration

  • permit_years_months

    • if False, durations with non-zero year or month components will raise a ValueError; otherwise, the ISO format will always be PT<seconds>S.

    • if True, year/month components will be accepted, and the ISO format will be P<years>Y<months>MT<seconds>S.

  • minus_sign_at_front

    Applies to negative durations, which probably aren’t part of the ISO standard.

    • if True, the format -P<positive_duration> is used, i.e. with a minus sign at the front and individual components positive.

    • if False, the format PT-<positive_seconds>S (etc.) is used, i.e. with a minus sign for each component. This format is not re-parsed successfully by isodate and will therefore fail duration_from_iso().

Raises:

ValueError

The maximum length of the resulting string (see test code below) is:

  • 21 if years/months are not permitted;

  • ill-defined if years/months are permitted, but 29 for much more than is realistic (negative, 1000 years, 11 months, and the maximum length for seconds/microseconds).

cardinal_pythonlib.datetimefunc.format_datetime(d: None | datetime | date | DateTime | str | Arrow, fmt: str, default: str | None = None) str | None[source]

Format a datetime with a strftime format specification string, or return default if the input is None.

cardinal_pythonlib.datetimefunc.get_age(dob: None | datetime | date | DateTime | str | Arrow, when: None | datetime | date | DateTime | str | Arrow, default: str = '') int | str[source]

Age (in whole years) at a particular date, or default.

Parameters:
  • dob – date of birth

  • when – date/time at which to calculate age

  • default – value to return if either input is None

Returns:

age in whole years (rounded down), or default

cardinal_pythonlib.datetimefunc.get_duration_h_m(start: str | DateTime, end: str | DateTime, default: str = 'N/A') str[source]

Calculate the time between two dates/times expressed as strings.

Parameters:
  • start – start date/time

  • end – end date/time

  • default – string value to return in case either of the inputs is None

Returns:

a string that is one of

cardinal_pythonlib.datetimefunc.get_now_localtz_pendulum() DateTime[source]

Get the time now in the local timezone, as a pendulum.DateTime.

cardinal_pythonlib.datetimefunc.get_now_utc_datetime() datetime[source]

Get the time now in the UTC timezone, as a datetime.datetime.

cardinal_pythonlib.datetimefunc.get_now_utc_notz_datetime() datetime[source]

Get the UTC time now, but with no timezone information, in datetime.datetime format.

cardinal_pythonlib.datetimefunc.get_now_utc_pendulum() DateTime[source]

Get the time now in the UTC timezone, as a pendulum.DateTime.

cardinal_pythonlib.datetimefunc.get_pendulum_duration_nonyear_nonmonth_seconds(d: Duration) float[source]

Returns the number of seconds in a pendulum.Duration that are NOT part of its year/month representation.

Before Pendulum 2.1.1, d.total_seconds() ignored year/month components, so this function will return the same as d.total_seconds().

However, from Pendulum 2.1.1, total_seconds() incorporates year/month information with the assumption that a year is 365 days and a month is 30 days, which is perhaps a bit iffy. This function removes that year/month component and returns the “remaining” seconds.

cardinal_pythonlib.datetimefunc.get_tz_local() Timezone[source]

Returns the local timezone, in pendulum.Timezone` format. (This is a subclass of datetime.tzinfo.)

cardinal_pythonlib.datetimefunc.get_tz_utc() Timezone[source]

Returns the UTC timezone.

cardinal_pythonlib.datetimefunc.pendulum_date_to_datetime_date(x: Date) date[source]

Takes a pendulum.Date and returns a datetime.date. Used, for example, where a database backend insists on datetime.date.

cardinal_pythonlib.datetimefunc.pendulum_duration_from_isodate_duration(dur: Duration) Duration[source]

Converts a isodate.isoduration.Duration into a pendulum.Duration.

Both isodate.isoduration.Duration and pendulum.Duration incorporate an internal representation of a datetime.timedelta (weeks, days, hours, minutes, seconds, milliseconds, microseconds) and separate representations of years and months.

The isodate.isoduration.Duration year/month elements are both of type decimal.Decimal – although its str() representation converts these silently to integer, which is quite nasty.

If you create a Pendulum Duration it normalizes within its timedelta parts, but not across years and months. That is obviously because neither years and months are of exactly fixed duration.

Raises:

ValueError

cardinal_pythonlib.datetimefunc.pendulum_duration_from_timedelta(td: timedelta) Duration[source]

Converts a datetime.timedelta into a pendulum.Duration.

cardinal_pythonlib.datetimefunc.pendulum_time_to_datetime_time(x: Time) time[source]

Takes a pendulum.Time and returns a datetime.time. Used, for example, where a database backend insists on datetime.time.

cardinal_pythonlib.datetimefunc.pendulum_to_datetime(x: DateTime) datetime[source]

Used, for example, where a database backend insists on datetime.datetime.

Compare code in pendulum.datetime.DateTime.int_timestamp().

cardinal_pythonlib.datetimefunc.pendulum_to_datetime_stripping_tz(x: DateTime) datetime[source]

Converts a Pendulum DateTime to a datetime.datetime that has had timezone information stripped.

cardinal_pythonlib.datetimefunc.pendulum_to_utc_datetime_without_tz(x: DateTime) datetime[source]

Converts a Pendulum DateTime (which will have timezone information) to a datetime.datetime that (a) has no timezone information, and (b) is in UTC.

Example:

import pendulum
from cardinal_pythonlib.datetimefunc import *
in_moscow = pendulum.parse("2018-01-01T09:00+0300")  # 9am in Moscow
in_london = pendulum.UTC.convert(in_moscow)  # 6am in UTC
dt_utc_from_moscow = pendulum_to_utc_datetime_without_tz(in_moscow)  # 6am, no timezone info
dt_utc_from_london = pendulum_to_utc_datetime_without_tz(in_london)  # 6am, no timezone info
cardinal_pythonlib.datetimefunc.strfdelta(tdelta: timedelta | int | float | str, fmt='{D:02}d {H:02}h {M:02}m {S:02}s', inputtype='timedelta')[source]

Convert a datetime.timedelta object or a regular number to a custom- formatted string, just like the strftime() method does for datetime.datetime objects.

The fmt argument allows custom formatting to be specified. Fields can include seconds, minutes, hours, days, and weeks. Each field is optional.

Some examples:

'{D:02}d {H:02}h {M:02}m {S:02}s' --> '05d 08h 04m 02s' (default)
'{W}w {D}d {H}:{M:02}:{S:02}'     --> '4w 5d 8:04:02'
'{D:2}d {H:2}:{M:02}:{S:02}'      --> ' 5d  8:04:02'
'{H}h {S}s'                       --> '72h 800s'

The inputtype argument allows tdelta to be a regular number, instead of the default behaviour of treating it as a datetime.timedelta object. Valid inputtype strings:

'timedelta',        # treats input as a datetime.timedelta
's', 'seconds',
'm', 'minutes',
'h', 'hours',
'd', 'days',
'w', 'weeks'

Modified from https://stackoverflow.com/questions/538666/python-format-timedelta-to-string

cardinal_pythonlib.datetimefunc.truncate_date_to_first_of_month(dt: date | DateTime | Arrow | None) date | DateTime | Arrow | None[source]

Change the day to the first of the month.