Problem/Motivation

I need to migrate data from a csv to a date field.
I don't see a solution for migration data to a date field with start and end dates.
I do see a solution for creating two date fields to handle start and end dates:

https://www.advomatic.com/blog/drupal-8-migrate-from-drupal-6-with-a-cus...

Proposed resolution

1. Make it possible to use two source fields in one migration against a single process plugin.
2. Create a date process plugin for handling dates in D8 core

Original report by generalconsensus

Comments

generalconsensus created an issue. See original summary.

quietone’s picture

heddn’s picture

Combining values from the source into a single array is possible right now

field_combine:
  plugin: get
  source:
    - start_date
    - end_date

The combined here is untested. The date range mapping below is tested.

And mapping them to a date range field is also possible.
https://gist.github.com/heddn/00f80f1ac388cd579bb8d142a4fe560f

  field_date:
    plugin: iterator
    source: @field_combine
    process:
      value: '0'
      end_value: '1'
josephdpurcell’s picture

Title: Drupal 8 Date Migration » Drupal 8 Date Range Migration
Version: 8.2.x-dev » 8.3.x-dev
Category: Support request » Feature request
Status: Active » Needs work
StatusFileSize
new0 bytes

Updating title to be specific to the feature. Updated attributes. Setting to 8.3.x since that is the latest core development branch.

I've attached an example date process plugin for 8.3.x (but will also work with 8.2.x) that needs some SERIOUS attention before it is ready for review, but it is a start.

The attached example assumes there is always a start and end date, that the imported date is a string parsable by PHP's DateTime constructor, and does not account for any of the date range field's configurations. It also needs a test, see core/modules/migrate/tests/src/Unit/process/ConcatTest.php perhaps.

Here is an example migration config yml:

process:
  ...
  field_date_range:
    -
      plugin: daterange
      source:
        - start_date
        - end_date

Where "start_date" and "end_date" are defined in my "source" as date values in format like "Wednesday, April 3, 2013".

josephdpurcell’s picture

Note: For some additional context, I attempted to use the solution in #3, but it failed with this error:

Argument 1 passed to Drupal\migrate\Row::__construct() must be of the type array, string given, called in /var/www/sites.local/docroot/core/modules/migrate/src/Plugin/migrate/process/Iterator.php on line 27 and defined Row.php:94

It does make me wonder if a process plugin is needed to solve the problem of a date range migration, I expect it would involve several steps: converting each start/end to a \DateTime, formatting them in Y-m-d to ensure it matches the storage format, and then concatenating the values into an associative array with keys "value" and "end_value".

On that front, I did attempt to debug the solution from #3 and found that if I modify core/modules/migrate/src/Plugin/migrate/process/Iterator.php::transform to force $value as a multi-dimensional array with $value = [$value], the migration works. I don't understand the migrate process functions well enough do be able to solve that, but it sounds like the solution from #3 is close.

In the mean time, I'm continuing to use the solution from #4.

jcisio’s picture

Status: Needs work » Active

Patch #4 was empty.

josephdpurcell’s picture

Status: Active » Needs work
StatusFileSize
new1.62 KB

Fail! Sorry about that.

Please see attached for the patch.

jofitz’s picture

Perhaps this could use many of the concepts in #2830296: Add migration plugin for Datetime_range field. Especially the tests.

jcisio’s picture

Re #8 interesting! Isn't that issue a duplicate of this one (with a difference is that it helps migrating from D7)?

BTW I managed to have it work without any patch. Just use subfield like this:

process:
  field_date_range/value: 'DATE START'
  field_date_range/end_value: 'DATE END'

With 'DATE START' and 'DATE END' are the begin/end date. Just pay a little attention (see #2820490: FormatDate process plugin) is to format the data properly. Either use the patch of FormatDate plugin, or in prepareRow for a date/time date range field:

$row->setSourceProperty('DATE START', gmdate(DATETIME_DATETIME_STORAGE_FORMAT, strtotime($date_start)));
$row->setSourceProperty('DATE END', gmdate(DATETIME_DATETIME_STORAGE_FORMAT, strtotime($date_end)));

If there is no time, use DATETIME_DATE_STORAGE_FORMAT constant instead.

So I think we can close this one and #2830296: Add migration plugin for Datetime_range field in favor of #2820490: FormatDate process plugin.

Edit: fixed typo.

heddn’s picture

Status: Needs work » Closed (duplicate)
Related issues: +#2820490: FormatDate process plugin

Agreed. That was my initial thought when I saw this issue opened, but as I hadn't done a migration yet of a date field from D6/D7, I wasn't certain. Closing duplicate to #2820490: FormatDate process plugin

josephdpurcell’s picture

Thanks for the ticket references Jo! In my searchings I didn't find those.

Per comments above it is clear that (a) other people are having trouble with date range migrations, and (b) there are parallel efforts to solve the problem.

Re #9 I had no idea you could do subfields--I did many permutations of #3 and maybe could have found a solution using subfields? In my #4 comments I explain that setting the subfield was what tripped me up.

I agree that we should condense down to 1 ticket if it makes sense, i.e. if dates and date ranges are handled similarly.

jcisio’s picture

Re #11: there was a typo in #9, I've just fixed that. The key is the format, because with the wrong format, there are still values in your database, but the field is empty when you edit, which could make you fill it does not work. I wasted hours because of that.

geosalameh’s picture

Hello,
Can someone please share a working example of a custom migration for date range.
Comment #9 seems working, but I am not able to put it in an working example.
Thank you.

josephdpurcell’s picture

@geosalameh, Here is an example YML config:

dependencies:
  module:
    - events_migrate
id: event
migration_tags:
  - CSV
migration_group: null
label: Event
source:
  plugin: csv
  path: modules/custom/events_migrate/assets/csv/events.csv
  header_row_count: 1
  column_names:
    -
      start_date: 'Start Date'
    -
      end_date: 'End Date'
    -
      title: 'Title'
    -
      description: 'Description'
process:
  type:
    plugin: default_value
    default_value: event
  title: title
  body: description
  field_date_range/value: start_date
  field_date_range/end_value: end_date
destination:
  plugin: 'entity:node'
migration_dependencies:
  required: {  }
  optional: {  }

I haven't validated this works, but per #9 it should work. Now, one problem you will most likely run into is the formatting of the date. For that, see #2820490: FormatDate process plugin. I asked a question there in comment #80 that didn't get answered. If my assumptions in that comment are correct then the only change to the above should be using a process plugin for the date range values like so:

...
  field_date_range/value:
    plugin: format_date
    from_format: 'm/d/Y'
    to_format: 'Y-m-d'
    source: 'start_date'
  field_date_range/end_value:
    plugin: format_date
    from_format: 'm/d/Y'
    to_format: 'Y-m-d'
    source: 'end_date'
...

ANd you would modify the "from_format" to whatever format your data is in. If you get too hung up on this, it may be worth manually editing your import data to the format "Y-m-d" and then you shouldn't need a process plugin.

heddn’s picture

geosalameh’s picture

Thanks guys for the help and sorry for the miscommunication, I am working on a migration from Drupal 7 to 8, so in this case what would be the required changes for the code/solution you've provided.
Thank you.

mullman’s picture

Here's how we got our migration working with a simple date range field:

id: Import_Event_export
label: Import Event
migration_tags: null
migration_group: testing
source:
plugin: csv
path: 'modules/custom/event/Event.csv'
header_row_count: 1
keys:
- id

column_names:
0:
id: 'id'
1:
title: 'Title'
2:
body: 'Body'
3:
Contact: 'Contact'
4:
Dates_Times: 'Dates and Times'
5:
Email: 'Email'
6:
Learn_More: 'Learn More'
7:
Location: 'Location'
8:
Phone: 'Phone'
9:
Summary_Virtual_Event: 'Summary Virtual Event'
process:
title: title
field_event_body: body
field_event_contact: Contact
get_date:
plugin: explode
source: Dates_Times
delimiter: ' to '
field_event_times_combined/value:
-
plugin: extract
source: '@get_date'
index:
- 0
-
plugin: format_date
from_format: 'l, F j, Y - g:ia'
to_format: 'Y-m-d\TH:i:s'
field_event_times_combined/end_value:
-
plugin: extract
source: '@get_date'
index:
- 1
-
plugin: format_date
from_format: 'l, F j, Y - g:ia'
to_format: 'Y-m-d\TH:i:s'

field_event_email: Email
field_event_learn_more: Learn_More
field_event_phone: Phone
field_event_virtual_yesno: Summary_Virtual_Event

type:
plugin: default_value
default_value: event

destination:
plugin: entity:node

Question: How do we get it working for CSV dates that have multi-day dates? For example, here is some CSV data - "Tuesday, May 31, 2016 - 8:30am to 4:30pm, Wednesday, June 1, 2016 - 8:00am to 4:30pm, Thursday, June 2, 2016 - 8:00am to 4:30pm, Friday, June 3, 2016 - 8:00am to 12:00pm"

We're stumped. Please help!

Thanks

mullman’s picture

Here's how our team ended up performing this. We cleaned up the CSV data and delimited the multi-day dates with a pipe character. The migration then uses sub_process to iterate through each of them and input the date ranges for each day.

Here's an example of one of these multi-day events in our CSV:
"Thursday, April 28, 2016 - 8:00am to Thursday, April 28, 2016 - 6:30pm|Friday, April 29, 2016 - 8:00am to Friday, April 29, 2016 - 1:30pm"

id: Import_Event_complex
label: Import Event
migration_tags: null
migration_group: testing
source:
plugin: csv
path: 'modules/custom/event/Event.csv'
header_row_count: 1
keys:
- id

column_names:
0:
id: 'id'
1:
title: 'Title'
2:
body: 'Body'
3:
Contact: 'Contact'
4:
Dates_Times: 'Dates and Times'
5:
Email: 'Email'
6:
Learn_More: 'Learn More'
7:
Location: 'Location'
8:
Phone: 'Phone'
9:
Summary_Virtual_Event: 'Summary Virtual Event'
process:
title: title
field_event_body: body
field_event_contact: Contact

field_event_times_combined:
-
plugin: set_datetime_array
source: Dates_Times
-
plugin: sub_process
process:
value:
-
plugin: format_date
source: '0'
from_format: 'l, F j, Y - g:ia'
to_format: 'Y-m-d\TH:i:s'
end_value:
-
plugin: format_date
source: '1'
from_format: 'l, F j, Y - g:ia'
to_format: 'Y-m-d\TH:i:s'

field_event_email: Email
field_event_learn_more: Learn_More
field_event_phone: Phone
field_event_virtual_yesno: Summary_Virtual_Event

type:
plugin: default_value
default_value: event

destination:
plugin: entity:node