It appears to be impossible to add a complete datetime attribute, one that saves both date and time. I've created the attributes with backend_type => 'datetime' and included custom frontend_input_renderers to get a calendar picker that has both date and time. Unfortunately, the time is always stripped off due to the hardcoded force for datetime attributes to be filtered by date. This occurs here.
Please let me know if there is an intended way around this. Seems like a lot of trouble to create an entire new backend type for this, when it should be stored in the catalog_product_entity_datetime table.
@maksek Max--can you steer this one to the right resource to investigate?
Internal issue MAGETWO-53559
Facing this one right now. Any ideas on when it will be corrected?
If interested I can post my work around. Includes a core helper rewrite and custom input renderer, not too much overhead.
@SamTay any chance you could post your solution.
I've been in contact with enterprise support and this issue is still being investigated by the core team.
@jzahedieh yea sorry for the wait. It's been a while, but I think these are all the pieces. Keep in mind this code is from like a year ago, I think we were still on 2.0.* or something. So for the helper preference, you might want to check on that copy-pasted stuff and make sure it's up to date.
Block/Adminhtml/Form/Element/Datetime.php
<?php
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
namespace BlueAcorn\ContentPublisher\Block\Adminhtml\Form\Element;
use Magento\Framework\Data\Form\Element\Date;
/**
* Class Datetime
*
* Created to allow full date+time picker on eav attribute. See \Magento\Backend\Block\Widget\Form methods
* _setFieldset and _applyTypeSpecificConfig for why this is necessary.
*/
class Datetime extends Date
{
/**
* Override to force date and time formats before rendering html
*
* @return string
* @throws \Exception
*/
public function getElementHtml()
{
$this->setDateFormat($this->localeDate->getDateFormat(\IntlDateFormatter::SHORT));
$this->setTimeFormat($this->localeDate->getTimeFormat(\IntlDateFormatter::SHORT));
return parent::getElementHtml();
}
}
Setup/InstallData.php
<?php
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
namespace BlueAcorn\ContentPublisher\Setup;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface
{
/**
* EAV setup factory
*
* @var EavSetupFactory
*/
private $eavSetupFactory;
/**
* Init
*
* @param EavSetupFactory $eavSetupFactory
*/
public function __construct(EavSetupFactory $eavSetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
}
/**
* {@inheritdoc}
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
/** @var EavSetup $eavSetup */
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->removeAttribute(\Magento\Catalog\Model\Product::ENTITY, 'publish_start');
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'publish_start',
[
'label' => 'Enable Start Date',
'type' => 'datetime',
'input' => 'date',
'input_renderer' => 'BlueAcorn\ContentPublisher\Block\Adminhtml\Form\Element\Datetime',
'class' => 'validate-date validate-date-range date-range-publish-from',
'backend' => 'BlueAcorn\ContentPublisher\Model\Entity\Attribute\Backend\Startdate',
'required' => false,
'group' => 'Product Details',
'sort_order' => 18
]
);
$eavSetup->removeAttribute(\Magento\Catalog\Model\Product::ENTITY, 'publish_end');
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'publish_end',
[
'label' => 'Enable End Date',
'type' => 'datetime',
'input' => 'date',
'input_renderer' => 'BlueAcorn\ContentPublisher\Block\Adminhtml\Form\Element\Datetime',
'class' => 'validate-date validate-date-range date-range-publish-to',
'backend' => 'Magento\Eav\Model\Entity\Attribute\Backend\Datetime',
'required' => false,
'group' => 'Product Details',
'sort_order' => 19,
'note' => __('If you want to keep a product enabled indefinitely, leave the start and end dates empty.'
. ' If an "Enable End Date" exists, the product will remain disabled after the end date has passed.')
]
);
}
}
etc/di.xml
<?xml version="1.0"?>
<!--
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper"
type="BlueAcorn\ContentPublisher\Helper\Adminhtml\Catalog\Product\Initialization" />
</config>
Helper/Adminhtml/Catalog/Product/Initialization.php
<?php
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
namespace BlueAcorn\ContentPublisher\Helper\Adminhtml\Catalog\Product;
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper as CatalogHelper;
/**
* Class Initialization
* Rewriting class to allow date+time on datetime attributes
*/
class Initialization extends CatalogHelper
{
/**
* @var \Magento\Framework\Stdlib\DateTime\Filter\DateTime
*/
protected $dateTimeFilter;
/**
* @param \Magento\Framework\App\RequestInterface $request
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter $stockFilter
* @param \Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks $productLinks
* @param \Magento\Backend\Helper\Js $jsHelper
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
* @param \Magento\Framework\Stdlib\DateTime\Filter\DateTime $dateTimeFilter
*/
public function __construct(
\Magento\Framework\App\RequestInterface $request,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter $stockFilter,
\Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks $productLinks,
\Magento\Backend\Helper\Js $jsHelper,
\Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
\Magento\Framework\Stdlib\DateTime\Filter\DateTime $dateTimeFilter
) {
$this->dateTimeFilter = $dateTimeFilter;
parent::__construct(
$request,
$storeManager,
$stockFilter,
$productLinks,
$jsHelper,
$dateFilter
);
}
/**
* Initialize product before saving
*
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Catalog\Model\Product
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function initialize(\Magento\Catalog\Model\Product $product)
{
$productData = $this->request->getPost('product');
unset($productData['custom_attributes']);
unset($productData['extension_attributes']);
if ($productData) {
$stockData = isset($productData['stock_data']) ? $productData['stock_data'] : [];
$productData['stock_data'] = $this->stockFilter->filter($stockData);
}
foreach (['category_ids', 'website_ids'] as $field) {
if (!isset($productData[$field])) {
$productData[$field] = [];
}
}
$wasLockedMedia = false;
if ($product->isLockedAttribute('media')) {
$product->unlockAttribute('media');
$wasLockedMedia = true;
}
$dateFieldFilters = [];
$attributes = $product->getAttributes();
foreach ($attributes as $attrKey => $attribute) {
if ($attribute->getBackend()->getType() == 'datetime') {
if (array_key_exists($attrKey, $productData) && $productData[$attrKey] != '') {
// BEGIN BA CHANGES
$dateFieldFilters[$attrKey] =
($attribute->getFrontendInputRenderer() == 'BlueAcorn\ContentPublisher\Block\Adminhtml\Form\Element\Datetime')
? $this->dateTimeFilter
: $this->dateFilter;
// END BA CHANGES
}
}
}
$inputFilter = new \Zend_Filter_Input($dateFieldFilters, [], $productData);
$productData = $inputFilter->getUnescaped();
$product->addData($productData);
if ($wasLockedMedia) {
$product->lockAttribute('media');
}
if ($this->storeManager->hasSingleStore()) {
$product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsite()->getId()]);
}
/**
* Check "Use Default Value" checkboxes values
*/
$useDefaults = $this->request->getPost('use_default');
if ($useDefaults) {
foreach ($useDefaults as $attributeCode) {
$product->setData($attributeCode, false);
}
}
$links = $this->request->getPost('links');
$links = is_array($links) ? $links : [];
$linkTypes = ['related', 'upsell', 'crosssell'];
foreach ($linkTypes as $type) {
if (isset($links[$type])) {
$links[$type] = $this->jsHelper->decodeGridSerializedInput($links[$type]);
}
}
$product = $this->productLinks->initializeLinks($product, $links);
/**
* Initialize product options
*/
if (isset($productData['options']) && !$product->getOptionsReadonly()) {
// mark custom options that should to fall back to default value
$options = $this->mergeProductOptions(
$productData['options'],
$this->request->getPost('options_use_default')
);
$product->setProductOptions($options);
}
$product->setCanSaveCustomOptions(
(bool)$this->request->getPost('affect_product_custom_options') && !$product->getOptionsReadonly()
);
return $product;
}
}
etc/adminhtml/events.xml
<?xml version="1.0"?>
<!--
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_validate_before">
<observer name="setStartDateMaxValue" instance="BlueAcorn\ContentPublisher\Observer\Adminhtml\Product\SetStartDateMaxValue"/>
</event>
</config>
Observer/Adminhtml/Product/SetStartDateMaxValue.php
<?php
/**
* @package BlueAcorn\ContentPublisher
* @version 1.0.0
* @author Sam Tay @ Blue Acorn, Inc. <[email protected]>
* @copyright Copyright 漏 2016 Blue Acorn, Inc.
*/
namespace BlueAcorn\ContentPublisher\Observer\Adminhtml\Product;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
/**
* Class SetStartDateMaxValue
* Observes catalog_product_validate_before
* Purpose: Set max value on start date equal to end date value
*/
class SetStartDateMaxValue implements ObserverInterface
{
/**
* Execute observer
* @param EventObserver $observer
*/
public function execute(EventObserver $observer)
{
/** @var \Magento\Catalog\Model\Product $product */
$product = $observer->getEvent()->getProduct();
$product->getResource()->getAttribute('publish_start')
->setMaxValue($product->getPublishEnd());
}
}
@SamTay thanks for providing all of this, but I didn't want to change the core functionality so drastically so as a work around I've created a second attribute to handle just time.
This attribute is static text and has a custom backend model that does some input validation, beforeSave sets the time on the specific datetime attribute, afterLoad parses the datetime attribute to get get just the time.
I've also raised this with the enterprise support team to see if there was an ETA for this fix but have heard nothing back.
Yeah, unfortunately I've found it necessary to rewrite lots of classes developing on M2. If we had a tool to mark a phpdoc for methods to check up on when upgrading (like the helper method above), I'd feel better about it. What's nice about the approach above is that you still get the native adminhtml datetime picker, and it behaves "as you'd expect" with a single attribute. But, hey, we pick our poisons haha, if that works for you, then it's a good solution. Good luck.
Hi @SamTay what do you think about wrapping your code to pull-request?
@SamTay I upgraded to 2.1.* and it is now not working. Do you know what do I need to change to make it work in 2.1?
Sorry, I don't develop on Magento anymore. I don't even think my team made it to 2.1 before I left. If you check the code I posted above and the link to the core code, compare it with 2.1, you might get somewhere.
I'm working on this.
I'm planning to develop a new option named "Date Time" for "Catalog Input Type for Store Owner" while creating a new attribute similar to "Date" option. Is this the right approach or there is any better approach recommended?
After debugging further I see the "Date" option is already of datetime type. Only issue is that time is hidden in admin section. May be because we want to show only date in admin section? If we want to show time also that can be enabled from app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/datepicker.js by setting showsTime to true .
@okorshenko Can you let me know how to go ahead with this?
@samtay, thank you for your report.
We've created internal ticket(s) MAGETWO-53559 to track progress on the issue.
@Szpadyzor thank you for joining. Please accept team invitation here and self-assign the issue.
Hi @novikor. Thank you for working on this issue.
Looks like this issue is already verified and confirmed. But if your want to validate it one more time, please, go though the following instruction:
Component: XXXXX label(s) to the ticket, indicating the components it may be related to.[x] 2. Verify that the issue is reproducible on 2.3-develop branchDetails
- Add the comment @magento-engcom-team give me 2.3-develop instance to deploy test instance on Magento infrastructure.
- If the issue is reproducible on 2.3-develop branch, please, add the label Reproduced on 2.3.x.
- If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and _stop verification process here_!
[x] 3. Verify that the issue is reproducible on 2.2-develop branch. Details
- Add the comment @magento-engcom-team give me 2.2-develop instance to deploy test instance on Magento infrastructure.
- If the issue is reproducible on 2.2-develop branch, please add the label Reproduced on 2.2.x
[x] 4. If the issue is not relevant or is not reproducible any more, feel free to close it.
Hi guys, any updates on this. Why is it not merged yet?
Hello, whats the status of this?
Hi @engcom-Charlie, can you re-confirm this issue on 2.4?
Thanks.
Hello @samtay we can't reproduce the issue on the latest 2.4-develop by followed steps:

SaveAR:
catalog_product_entity_datetime table contains both date and timeCould you please take a look?
Thank you.
I opened this issue in 2016... haven't worked on Magento in years. Congrats though!
Thanks for the reply @samtay, closing this issue as it is not reproducible anymore.
Most helpful comment
@jzahedieh yea sorry for the wait. It's been a while, but I think these are all the pieces. Keep in mind this code is from like a year ago, I think we were still on 2.0.* or something. So for the helper preference, you might want to check on that copy-pasted stuff and make sure it's up to date.
New datetime form element
Block/Adminhtml/Form/Element/Datetime.php
Use it in install script
Setup/InstallData.php
Overwrite product initialization helper
etc/di.xml
Helper/Adminhtml/Catalog/Product/Initialization.php
If you are implementing start/end, set max value stuff
etc/adminhtml/events.xml
Observer/Adminhtml/Product/SetStartDateMaxValue.php