6
$\begingroup$

When using the Wolfram Client Library for Python in a Python program there appears to be no built-in function to convert a DateObject in to a Python datetime object.

from wolframclient.evaluation import WolframLanguageSession
from wolframclient.language import wl, wlexpr

wolfSession = WolframLanguageSession()

With date_values as DateObjects in Python

dt = wl.DateObject([2019,1,1,0,15]);
date_values=wolfSession.evaluate(
    wl.DateRange(
        dt,
        wl.DatePlus(dt, [1*15, 'Minute']),
        wl.Quantity(15, 'Minute')
    )
);

print(date_values)
[DateObject[[2019, 1, 1, 0, 15], 'Minute', 'Gregorian', -3.0], DateObject[[2019, 1, 1, 0, 30], 'Minute', 'Gregorian', -3.0]]

The only way I have found to convert these is by parsing the "ISODateTime" string from DateString in Python.

import iso8601

p_dates=list(map(
    lambda d: iso8601.parse_date(d), 
    wolfSession.evaluate(
        wl.Map(
            wl.Function(wl.DateString(wl.Slot(1),'ISODateTime')),
            date_values
        )
    )
));

print(p_dates)
[datetime.datetime(2019, 1, 1, 0, 15, tzinfo=datetime.timezone.utc), datetime.datetime(2019, 1, 1, 0, 30, tzinfo=datetime.timezone.utc)]
wolfSession.terminate()

This seems verbose considering many types automatically convert between a Wolfram session and Python (and vice versa).

Am I missing a built-in function to do this? If not, is there a terse method of converting a list of Wolfram DateObjects into Python datetime objects in Python?

$\endgroup$
2
  • 1
    $\begingroup$ You should look in the WolframClientLibrary code, esp. here. The library appears to provide a way to automatically convert types over by registering a consumer object. By knowing the structure of a Mathematica DateObject you can directly construct the datetime one. I know this isn't terse, but it is extensible and really the way things like this ought to be done in general. $\endgroup$
    – b3m2a1
    May 28, 2019 at 18:43
  • $\begingroup$ @b3m2a1 Interesting. I think I will give it a try. $\endgroup$
    – Edmund
    May 28, 2019 at 23:10

1 Answer 1

3
$\begingroup$

I just happen to deal with such cases recently and have done some preliminary work, which is hoped to be helpful.

Please see the code below:

  1. TimeSeries consumer:

# TimeSeries in wl
class TimeSeriesConsumer(WXFConsumer):
    """Implement a consumer that maps TemporalData to pandas.series with DatetimeIndex."""

    # represent the symbol TemporalData as a Python class
    TemporalData = wl.TemporalData

    def build_function(self, head, args, **kwargs):
        import datetime
        import numpy as np
        import pandas as pd

        if (
            head == self.TemporalData
            and len(args) == 4
            and args[0].name == "TimeSeries"
        ):
            ar_data = np.array(args[1][0])[0]
            dt = args[1][1][0]
            start, end = [
                datetime.datetime.strptime(
                    "-".join([str(x) for x in x_dt]), "%Y-%m-%d-%H-%M-%S.%f"
                )
                for x_dt in dt[:2]
            ]
            freq = pd.to_timedelta(*dt[-1])
            idx_dt = pd.date_range(start, end, freq=freq)
            ser_dt = pd.Series(ar_data, index=idx_dt)
            return ser_dt
        # otherwise delegate to the super method (default case).
        else:
            return super().build_function(head, args, **kwargs)

  1. DateObject consumer:
# DateObject in wl
class DateObjectConsumer(WXFConsumer):
    """Implement a consumer that maps TemporalData to pandas.series with DatetimeIndex."""

    # represent the symbol DateObject as a Python class
    DateObject = wl.DateObject

    def build_function(self, head, args, **kwargs):
        import datetime

        if head == self.DateObject:

            list_dt = [0, 1, 1, 0, 0, 0]
            for i, x in enumerate(args[0]):
                list_dt[i] = x
            str_dt = "-".join([str(x) for x in list_dt])
            dt = datetime.datetime.strptime(str_dt, "%Y-%m-%d-%H-%M-%S.%f")

            # when timezone is available
            if len(args) == 4:
                utc = args[-1]
                delta_dt = pd.to_timedelta(utc, unit="h").to_pytimedelta()
                dt = dt.astimezone(tz=datetime.timezone(delta_dt))
            return dt
        # otherwise delegate to the super method (default case).
        else:
            return super().build_function(head, args, **kwargs)

For example, to convert a TimeSeries into a pandas.Series, one can use the above as follows:

ser_sim = binary_deserialize(
        wl_session.evaluate_wxf(exp_wl), consumer=TimeSeriesConsumer()
    )

where exp_wl is the wl TimeSeries object.

$\endgroup$
1
  • $\begingroup$ Great answer (+1) $\endgroup$
    – Edmund
    Dec 23, 2021 at 14:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.