New module 'Date'

This commit is contained in:
Manuel Cillero 2017-07-26 14:11:39 +02:00
parent a4887c6502
commit ac4b84765b
116 changed files with 30150 additions and 0 deletions

View file

@ -0,0 +1,795 @@
Date Module 6.x
=================
Version 6.2.x-dev
=============
- Issue #1289270 by pdrake: Fixed date arguments and filters do not work with relationships.
- Issue #973924 by arlinsandbulte: Fixed jquery_ui() dependency not in .info file.
- Issue #895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc.
- Issue #1192020 by tim.plunkett: Fixed Date granularity is too fragile in date_field_all_day().
- Issue #501416 by refman1073: Fixed Date validation incorrect for days > 31.
Version 6.2.9
=============
- Issue #1005968 by jcmarco, dnotes: Added Ajax for date navigation attachment views.
- Issue #1545254, Use remember in filter only if a value exists.
- Add a test for force_value to the code that checks the default value for the exposed filter. In some cases the relative value wasn't overriding a fixed value as it should have been doing.
- Issue #1223956 by mikeytown2 and hyrcan, Fix undefined index in date_handler_fields().
- Issue #1517548 by joachim, Fix incorrect documentation for date_default_timezone_name().
- Issue #884310 by AlexisWilke, Postgres earlier than 8.3 needs different syntax in date update.
- Issue #606658 by johnmunro and KarenS, Make sure ical import processes multi-day all-day events correctly.
- Issue #1282538 by benjifisher, crifi, and KarenS, Make sure ical feed for all day events adds 1 day to the end date to comply with the spec.
- Issue #1463438 by hyrcan, Fix some 5.3 notices.
- Issue #1513396 by JoeMcGuire, Fix failing Date Popup test.
- Clean up to eliminate PHP notices when running tests on PHP 5.3.
- Issue #1408216 follow up, Need to be sure that NULL is the default state for the sql functions.
- Issue #337666, Make sure the UNTIL date for repeating dates is inclusive.
- Backport the D7 version of date_ical.ical.inc to D6 so we can keep D6 and D7 synched up more easily.
- Issue #975320 by mvc, Add translation to jQuery date picker.
- Issue #1408216, Remove remaining references to ->offset, which is no longer defined, nor needed. The timezone defaults should be properly set by date_views_set_timezone().
- Issue #1408216 by technicalknockout, artkon, tangent: Fixed Events no longer appearing in calendar (regression introduced in Issue #1017866).
- Issue #1360672, Fix limit format code to remove leftover non-ascii text as well as leftover ascii text.
- Issue #1033856 by joachim: Fixed conflicting messages about jquery UI requirement.
- Issue #1049992 by jjwhitney: Fixed Failed validation causes values to disappear when using popup widget.
- Issue #436490 by kong, xjm: Fixed Add support for jQuery update to 1.3.x and jQuery UI 1.7.x.
- Issue #1161814 by Scyther: Bad code in function date_field().
- Issue #232954 by rho_, arlinsandbulte: Fixed text 'Empty 'To date' values will use the 'From date' values' is always visibl under the second row of selectboxes ('date - to').
- Issue #536832 by aturetta: Fixed Date format types not translated in admin pages.
- Issue #488078 by emmajane: Fixed Broken link.
- Issue #1017216 by tim.plunkett, arlinsandbulte: Added custom date format without time shows 'all day'.
- Issue #1407064 by nairb: Fixed undefined function drupal_substr_replace().
- Issue #1162962 by mikeryan: Fixed E_STRICT notices in date_make_date().
- Issue #554546 by master-of-magic: Fix Timezone list translation
- Finally found why tests are failing on d.o. Some of the needed modules aren't getting set up in the test.
Version 6.2.8
=============
- Make sure the value passed to set the starting nid for converting events is an integer.
- Explicitly turn 'date_popup_timepicker' to 'none' in tests to avoid Date Popup test failures.
- Issue #1300274, Backport all the fixes for date_repeat_build_dates() from D7 so additions will work right in PHP 5.3.
- Issue #1130814, Backport all the fixes for Date Repeat Calc from D7 so PHP 5.3 will work right and tests will pass.
- Issue #1391374 & #1302052 by benjifisher, CRLF line endings cause problems with git apply
- Issue #1101284 by pfournier, Expand regex for Month names to catch more possible variations.
- Issue #1086100 by ayalon, Group Multiple Value functionality is broken
- Issue #752550 by Fonant, Week number gets printed twice
- Issue #1038482 by somanyfish, iCal import failing due to colon instead of semi-colon
- Issue #1052586 by jpsolero, Problem with Date API when using Calendar with argument set to "Week" granularity and "current date" default argument
- Issue #307274 by ksenzee and arlinsandbulte: Limit the selectable date range using absolute values instead of only the relative Years back and forward:
- Issue #742146, Add option to remove X-WR-CALNAME if VEVENT is not a feed.
- Add option to change method from PUBLISH to REQUEST in VCALENDAR.
- Issue #1087798 by anj, Fix X-WR-CALNAME in VCALENDAR.
- Issue #1017866 by KarenS and Jason89s, Make sure views argument and filter don't do timezone adjustment for dates that don't have time.
- Issue #1204988 by paulsheldrake, Remove IE6 code from 1.7 datepicker CSS.
- Issue #1159404 by mikeryan, Fix incorrect call to parse an rrule in date_repeat_build_dates().
- Issue #1160614 by joelstein, Make sure date repeat rule gets split correctly no matter which line endings were used.
- #1018412 by omerida: get_class() called without object
- #1054458: Move date_increment_round() function into Date API module so it is always available.
- #745074, by rsevero: Don't test for valid timezone-adjusted date for dates that don't use time.
- #1082658: Saving the options as arrays breaks other things. Add a custom export plugin instead.
- #1082658, Views options need to be declared as arrays or they are not saved in the export in Views 3.
- #945982 by tstoeckler, Fix another PHP 5.3 warning.
- #1027752 by B-Prod and dboulet, Fix missing table join on argument summary view.
- #686394 Fix computation of week when week is used as default value of date argument.
- #447868 Figure out when remembered value needs to used in Date filter.
- #430746 by Kermit, Fix missing datetime type in event migration code in Date Tools.
- #1011184 Add function to sort granularity array and use it in date_make_date().
- #1014664 Fix timezone shifts when computing UNTIL date for repeating dates.
- #1014818 Fix bug that only affects old versions of php to show all dates as the current date. Those old versions cannot be made to work the same way, fall back to a less robust workaround.
- #580176 Fix alias handling when dates from multiple content types are used in same view.
- #1013662 by developer-x: Remove check for start and end date - this prevented multiday-all day events from showing 'All Day'
- Add more tests.
Version 6.2.7
=============
- #514242 Day repeated every two weeks was broken by wrong day comparison value.
- Fix a bug in the calculation of the week days when using ISO weeks.
- #549042 The date_week() function should return an integer even when using ISO weeks.
- #575770 Fix handling of empty vs zero date parts in the date combo processing.
- #385688 by jcmarco, Make sure we don't set relationships where there should be none.
- Fix a strict warning for use of is_a().
- #882980 Fix WSOD in PHP4 from date_format process to create a new date.
- #998498 Don't try to use jquery_ui function in update if it isn't installed, add a warning message.
- #993148 Use url() for link to date format page in date widget settings so it works without clean urls.
- Add more date repeat tests and fix date repeat handling of day of month > 5.
- #784854 by akeemw, fix broken date repeat handling of negative by-day computations.
- Add functions to get a granularity precision from a granularity array or an array from a precision.
- #760284 by skwashd, arithmetric clean up ical rfc compliance.
- #939152 by amelfe add z-index to datepicker 1.7.
- #826458 by troynt Keep date popup from popping up on page load.
- #441970 by recrit and patcon, tweak year range validation in date filter and date argument to be more flexible.
- #952036 by cwc, Date popup defaulted to earliest year instead of current year when using jquery ui 1.7.
- #977002 Ensure that the year range always sets the minium year as the minimum even if the year range string has it reversed.
- #848656 by Mark Trapp, remove use of ereg from date_calc for compatibility with PHP 5.3.
- #385688 by christianchristensen Fix filter handling for relationships.
- #963844 Fix the date filter and argument so they will show up both under the 'Date' group and as 'Content' fields.
- Add tests for date popup widget.
- #560054 by travist and AllPlayers.com, add AHAH Add More buttons to date repeat exceptions and additions.
- #292522 by chaps2, fix typo in new date repeat additions feature.
- #292522 by chaps2, add additive exception to repeating date form.
- Avoid errors with incomplete time values in dates.
- #337666 Fix UNTIL and EXCEPT dates to not show time and automatically set them to 23:59:59 before storing.
- #769064 Fix translation in format example text.
- #898024 by jaydub and #834388 by arroncouch Fix the jquery ui css paths.
- Adding and updating test, including a test provided by tristanoneil, with a few fixes uncovered by the tests.
- #521990 by Aron Novak: Make sure popup date selector grid is in correct year by.
- #587338 by Kevin Rogers - Add token to support day number with English ordinal (st, nd, rd or th).
Version 6.2.6
=============
- Revert #802046. Unicode support change for date parsing causes serious errors.
Version 6.2.5
=============
NOTE: The included copy of the jQuery UI datepicker in the Date Popups module has been removed. It is now necessary to install the jQuery UI module (http://drupal.org/project/jquery_ui) for the Date Popups datepicker to work!
- #436490 Add support for the jQuery UI 1.7 datepicker.
- #802046 by t3hk0d3, fix date parsing to better handle unicode characters.
- #475926 by hawleyal, clean html out of ical export.
- #675352 by sdague, clean up ical escape text.
- #848644 by Crell, make sure ical_parse_rrule can handle embedded timezones.
- #784652 by keinstein, fix 'r' format.
- #679224 by brianV, add defaults for month and year for latest jQuery UI datepicker.
- #518816 by dww, avoid 'date_format() expects parameter 1 to be DateTime' errors by checking for date class.
- #772180 by recrit and dww, make sure date descriptions always use valid dates for format examples.
- #642018 by slip and KarenS, make sure to eliminate spaces before AM/PM in date popup timepicker.
- #582982 by yahn, make sure date_all_day gets the most significant date element.
- #785708 by bdurbin and locomo, update jquery timeentry to latest version.
- #390012 by joachim, Make sure view args are sorted when creating navigation links.
- #408190 Fix mini calendar title link so it doesn't always link to current date.
- #630972 by bec, make sure a default system timezone is set to make PHP 5.3 happy.
- #641344 by arlinsandbulte, add option to use ISO weeks instead of calendar weeks.
- #697144 by tstoeckler, date_format_date formatter fixes.
- #774182 by keinstein, fix some strict error handling errors.
- #702922 by rupl, add missing class to date popup.
- #660776 by neilnz, fix postgresql sequence update.
- #645936 by markus_petrux, allow other modules to add #afterbuild to user forms.
- #406976 by dopry, Check both the field name and type name to get the right label for shared fields.
- #599744 by anarcat, don't rename table to one that already exists.
- #599484 by Gribnif, avoid infinite date repeat in PHP4.
- #529734 by raintonr, reduce number of requires for PHP4.
- #875978 by mikeryan, avoid notices when updating nodes with empty datefields.
- #548866 by siritree, fix cannot use scalar value as array by testing that $item is an array.
- #706006 by Gribnif, fix broken logic for every Nth DAY.
- #460776 by Mr. Chips, fix ordinality in Date Repeat string.
- #635778 by dagmar, fix typo keeping comment dates from showing up.
- #819678 by jonathan1055, fix url on admin page to work without clean urls enabled.
- #553884 by divinehawk, fix pgsql timezone support.
- #320765 by joelstein, fix order of timezone name list.
- #552154 by Ian Ward, make date_first_day defaults consistent.
- #477868 Exposed select filter widget sometimes gives 'illegal choice' errors.
- #385688 by Gribnif, adding a set_relationship() function to date filter and date argument to try to fix broken relationships problems.
- #580178, Remove http://drupal.org/cvs?commit=263582, might be a problem.
- Fix default values for date_fields, should be array.
- Rename incorrectly named views definition.
- #313704 Add timezone information earlier so that auto_nodetitle has it, patch by Kars-T.
- #554546 Don't put translated timezone names in cache, patch by alex.k
- #491954 Make sure translation works in date changer, patch by Bodo Maass and cyp25.
- #675234 Try to convert deprecated timezones in ical, patch by David Goode.
- #535270 Test for existing date format tables before update, patch by anarcat and Owen Barton.
- #545466 Fix bid placeholder in date wizard, patch by colinsherry.
- Be sure to set timezone when changing date type. Clean up unused parts of code from Date Wizard.
- More work on repeating dates.
- Adding experimental tool to change the date type.
- Move event import and date wizard into separate files.
- #698522 Make Views code work with Views 2 and Views 3, patch by rjbrown99, Ahqar, tauno, dagmar, perusio.
- Coder review fixes.
- Install file cleanup.
- #489494, by poiu, Some RTL css fixes.
Version 6.2.4
=============
- #565830, by bengtan, Fix Postgres handling of hour '0'.
- #408770, by vkareh, Make sure dates with access control retain values during processing.
- #574876, by stella, Fix odd logic when forcing views dates in date elements.
- #385688, by noahb, a fix for using relationships with date filters and arguments. Thanks!
- #535008 Prevent possible PostgreSQL problem by re-arranging timing of dropping the key.
- #535270 Fix install test for update 6005 that kept it from completing.
- We sometimes get $edit without $edit['date'] from Views exposed filters, fix processing for that possibility.
- Exposed filter querystring must be appended in normal views.
Version 6.2.3
=============
- #494350 Add 'c' format.
- #352975 Remove filter:mask in datepicker css and add help in settings on how to add back in theme.
- #352975 Add hook_requirements warning to suggest using jQuery UI for datepicker.
- #529826 Change date tools permission name to add 'administrator' to make it clear it has important powers.
- #529826 Check for invalid content type name in Date Tools.
- #529826 Add check_plain to label.
- #418874 Don't run timestamp format through date_limit_format().
- #456308 Don't reset date_api_fields cache over and over.
- #424006 Add flexibility to date filter options by passing them directly to date_create.
- #465870 Make sure date_repeat_form.inc is included in validation.
- #453688 Use is_a() function so date handler will work for any handler derived from views_handler_field_date.
- #452934 Make sure format_interval() doesn't do anything with empty dates.
- #483682 Make searches case-insensitive.
- #395156 Change table name for date formats from 'date_format' to 'date_formats' because 'date_format' is reserved word in some dbs.
- #342357 Make jQuery timepicker optional.
- #473308 Add handling for year-only date stored in timestamp field.
- Get rid of <br> in admin summary that was going through check_plain.
- #482436 Removed deprecated code to remove empty field values, CCK does that now.
- #421258 Don't cache prepared node, it may need to change context or content type.
- #421258 Make sure formatter settings context is never empty.
- Add theme date_time_ago() to Date API so other modules can use it.
- Remove deprecated date_format_options().
- Remove deprecated Views code.
- Remove deprecated date_install_clear() function.
Version 6.2.2
=============
- #418174 Timestamp format should not be run through date_limit_format().
- #456308 We do not have to reset the date_api_fields cache over and over!
- #424006 Add flexibility to date filter options by passing them directly to date_create() for PHP versions > 5.2.
- #465870 Make sure date_repeat_form.inc is included in validation.
- #453688 The is_a() function makes date handler api work for any handler that was derived from 'views_handler_field_date'.
- #452934 Make sure format_interval doesn't try to do anything with empty dates.
- #483682 and #395156 Have to alter the collation of the format fields in MYSQL so they don't do case-insensitive searches and indexes. Fixing table name at the same time to fix another sporatic problem where 'date_format' is treated as an invalid table name and the table is not created.
- #342357 Make jQuery Timepicker optional.
- #466222 Fix Dutch translations.
- #473308 Add handling for year-only date stored in timestamp field.
- #482436 Remove deprecated code to remove empty field values, CCK does that now.
- #421258 Don't cache prepared node, it may need to change by context and/or content type.
- #421258 Make sure formatter settings context is never empty.
- Add new calendar-day theme to format date like a calendar day page, move basic theme date_time_ago() from Date module to Date API so other modules can use it.
- Remove date_format_options() from Date API, no longer used with new format system. Move it to Date Popup, which is the only module still using it with TODO to maybe get rid of it.
- Remove old views code from Date module.
- Remove date_install_clear() function, a leftover from an earlier version no longer needed.
- #451858 Fix date_timezone_set() when date is empty while adding new item.
- Fix date_limit_format() to be sure the 'T' is stripped out of ISO dates with only date or only time.
- #432368 Don't force default value in exposed filter if default is empty.
- #369020 Switch '+1 Sunday' to 'First Sunday' to over PHP5 bug.
- #444960 Fix Date Tools permissions.
- #315443 Make sure 'within' labels don't break when used in text fields.
- #423710 Make date navigation more accessible with titles.
- #424388 Pass #attributes from element to Date Popup.
- #381370 Remove escaping for colon and double quote per ical spec.
- #426810 Fix HTML syntax in advanced help files.
- #456460 Break out simple themes for date_display_single and date_display_double for easier control by themers of separator and other html.
- #384258 Fix broken handling of 24 hour time in Date Popup.
- #401152 Fix broken handling of long month name in input format.
- #452420 Revert change of hyphen to dash in date display, dash is handled badly by token and elsewhere.
- #347080 Using 'first' and 'last' is more reliable than '+1 week' in strtotime().
- #369020 Fix bug when finding first or last Mon/Tue/Wed/Thu/Fri/Sat/Sun of month.
- #427830 Postgres can't handle \T.
- #414446 Avoid php errors when context is missing.
- #444272 by Darren Oh, add Date views conversion from Views 1 to Views 2.
- #428828 Make sure date $fields array always has expected format, even when empty.
- #341463 Repeating date with no To date was missing form label.
- #404866 Make sure labels are translated.
- #442526 Add missing space to navigation template.
- #375864 Clean up date_limit_format() to handle more odd formats.
- #447616 Allow custom tables as well as custom fields to use Date argument and filter.
- #338253 Fixes to translation of timezone names.
- #409476 Make sure empty UNTIL date is not parsed into RRULE.
- Make sure $base is never empty when identifying date fields for date filter and argument.
- #317105 Update Isreal DST rules.
- #408770 Abort date_combo processing when user has no #access.
- #388728 Change dash to en dash in date range theme.
- #414290 Update timezone form_alter to account for difference between Event version 1 and 2.
- Broke un-exposed filters when fixing exposed filters, fixing them again.
- Non-node date fields need better descriptions and field limits.
Version 6.2.1
=============
- #411496 Select widget in Views was not getting forced to the right value when using 'now+' defaults.
- #395156 Hide errors during multiple passes of date_format creation.
- #395156 Update new format table definitions to be sure indexes are not too long for MYSQL to create the tables.
- #365771 Make sure date filter doesn't try to use Date popup widget if that module is disabled.
= #406876 Adapt Views block argument handling to adapt to arguments like user/%/calendar/2009-05.
- #385880 Add non-node date handling for Views arguments and filters.
- #400992 Add time and datetime tokens.
- #408176 Fix token handling of empty values.
- #399180 Test for Views tables before uninstalling them.
- #369811 Fix problems with duplicate date formats in new format system.
- #354989 Make sure year-only date does not regress to YYYY-00-00 00:00:00, which will end up as previous year.
- #398258 Fix problems handling blank text widget values and general form processing cleanup.
- #405408 Update simpletests.
- #397420 Fix miscalculation errors in date_difference.
- #360967 Add method for deleting a custom default calendar.
- #376224 Add 'From date' and 'To date' to Views titles.
- #375864 Make sure date_limit_format() doesn't keep formats that are only escaped strings.
- #386124 Updates that test for Views version should work even if Views is not installed.
- #386406 Date filter was not picking up right default value in some situations.
Version 6.2.0
=============
- #376880 Move back/next symbols back into link.
- Exposed filter was not testing empty values correctly.
- Add Advanced help for new formatter settings.
- Add formatter settings for node and view to control which multiple value to display when.
- Improve interpretation of argument date ranges.
- Improve handling of translation for Date repeat rules.
- #347645 Mark 'To date' required if it is.
- #345215, #309921 Rework the way multiple 'From date' and 'To date' fields are identified in validation errors.
- #375551 The 'All day' formatting should not be applied to format_interval formats.
- Date PHP4: fix inconsistency in offset computation for first two hours of the day dst changes.
- Date PHP4: eliminate some needless cycles to improve performance.
- Add test files to import RRULEs in .ics, all day dates in .ics, and date info in .csv formats used by Yahoo Calendar and Outlook.
- iCal TRIGGER can either be a date or a duration.
- Fix to iCal: alter the end date of all day events to match the to date.
The ical practice of setting the End date as the next day won't work for our interal use of this value.
- Fix to iCal: leave 'all day' notice in the individual date components as well
as the top level so it is available in each portion of the VEVENT array, needed for FeedAPI parsing.
- Fix broken date link in calendar created by Date Tools.
- Make the default value for the 'To date' easier to see in the field settings.
- Update date argument summary for recent changes in Views.
- #345077 Make sure duplicate copies of the date field each get to theme the raw, unaltered, data.
- #375551 Fix fatal typo in reworked date_format_date() function.
- #338253 Re-work the Date repeat description to provide more context for translations.
- #338253 Re-work date_t() to create a more robust method of handling translations of abbreviations and other short strings.
- The date filter should not be setting the view dates, the style should do that.
- Add 'value' and 'value2' hints to views fields and use new CCK views data.
- Give the Date browser a better path with no underscore.
- #370961 Date format update was not PostgreSQL-safe.
- #337301 Move hook_schema_alter from date_api.module to date_api.install.
- #367688 Fix date format css to display remove link better.
- Add our custom date formats as values on the core locale form so we don't get errors on submission if they are missing.
- #348241 Don't unset view copy on node because it interferes with template suggestions. Need to watch this for possible performance problems.
- #364770 Add new date link needs to convert underscore to hyphen.
- Can't use date-clear attribute on timezone list.
- #370092 Add all-day checkbox to date field and use it in calendar.
- #347090 Fix southern hemisphere timezone adjustment for PHP4.
- Make sure un-exposed filter checks for defaults to find values like 'now'.
- Fix bug that caused date_select to end up as current date.
- #364919 Fix typo in html link for date format information.
- #360670 Rework date_range to better handle absolute as well as relative years in argument and filter.
- #367970 Fix date filter bugs, better handling for partial dates and force date popup to use the right default value.
- #313369 Repeating date with exceptions created error when exceptions data is appended.
- #363424 Empty value should be object, not array.
- Fix bad PHP4 regex that caused endless looping in calendar and broken logic elsewhere.
- by stella, nedjo, grugnog2, and catch: #318008 Create new system for handling flexible date formats that can be adapted by locale.
- Make sure prev/next links point to the right path when another argument precedes the date argument and is empty.
- Don't try to set date argument when another argument precedes it.
- Make sure theme functions can be discovered even when called from unexpected places, like embedded views.
- Make the PHP4 code forgiving enough to accept '+ 1 month' instead of '+1 month'.
- Overcome Views form handling to make sure text and Date popup exposed filters will default to the right values.
- #348276 Remove class date-clear-block from timezone element.
- #217153 Remove asterisks from custom format now that they are explained in advanced help.
- #292942 Only load timezone js file in timezone forms.
- #313427 Don't add the date field to the view, it may be enough to join the table in.
- #356564 Add Date popup option for exposed date filters.
- #349306 Incorporate year range into date filter year option.
- #312403 Add NULL/NOT NULL handling to date filter.
- #356305 Date filters needed a rewrite to properly handle between with more than one date field.
- #350656 Fix broken handling for 'time ago'.
- #307578 Add permissions for viewing date repeats tab.
- Clean out unused part of Date Popup jQuery code for easier maintenance.
- #347080 Fix PHP4 southern timezone computation and New Zealand region.
- #353327 Check for Event 2 timezone table before trying to use it.
- #310633 Rework 'all day' logic to correctly handle increments.
- #345953 Fix report of update failure from missing content_check_update() function.
- #348157 Beautiful new datepicker popup themes and images from hass. Thanks!
- #357226 Don't translate menu items.
- #357094 Remove extra parenthesis.
- #346752 Add RTL css code.
- #324290 Don't validate when $items is empty.
- #348375 Add missing php tags in template.
- #347878 Translation fixes.
- Add new module 'Date tools' with date wizard to easily create a simple date content type with an auto-configured related calendar.
- #313704 Make sure date db timezone does not get removed before token handling.
- #313704 Fix token 'From date' value that was using 'To date' value.
- #327897 Fix To-view token in date_token.inc
- #345862 Fix missing close comment tag in datepicker css.
- Date input and display formats were not properly defaulting to the site default values.
- Improve the template and css for the exposed date filter to keep date parts lined up next to each other.
- Fix handling for fixed date default value in date filter.
- Get rid of DURATION time part details in iCal array, we only need the DATA and DURATION.
- #282521 Do more work on LOCATION to split out UID and handle odd upcoming.org location identifiers.
- Add more error trapping to ical parser.
- Add handling to ical parser for odd upcoming.org location format.
- #341705 Fix broken iCal DURATION parsing.
- Add some protection against empty or missing info in ical parser.
- #343126 Fix date argument handler type assignment.
- #343190 Add validity testing to date_api_ical_build_rrule().
- #307166 Make sure timestamps with missing granularity parts in select widgets get defaulted to empty values and saved correctly.
- #341203 Make sure there is no SQL error if there are no fields.
- Add ability to analyze a period to period argument, like P1M--P1Y.
- Adapt Date Argument/Date Browser code to work correctly if only a period is used.
- #324063 Remove unneeded break.
- #320596 Add hook to date_api_fields() so custom modules can add date fields to the Date argument and filter.
- #341964 Get missing back/next links back in year.
- Add date_increment_round() to date_content_generate to make sure generated dates match increment settings.
- Move Date repeat form help to Advanced Help instead of using collapsed fieldset.
Version 6.2-rc6
=================
- #341202, #339457, #337952, #329128, #337787, #341203, Fix repeating and multiple date displays.
- #273093 Add time formatter option to use in calendar views.
- #324063 Unset invalid values in the date argument position of the url to avoid various errors in the date argument handling.
- #301279 Remove README.txt for Date Popup, it's out of date and I'm moving documentation into the d.o handbook and Advanced Help.
- #337075 Change background-color:none to background-color:transparent.
- #324756 Bypass validation for empty, non-required dates.
- Fix broken Date API test broken by change in API.
- #340394 Fix four-digit year format for PostgreSQL.
- Fix bug in new date_field_get_sql_handler() helper function to pick up the right timezones for the handler.
- #338277 Make some improvements to datepicker css.
- #326722 Update the datepicker js to the latest version to fix bugs in current version.
- Date picker can't handle some year ranges that work in Date API, so adjust it as needed.
- #324150 Fix broken 'not between' date filter handling.
- #337784 Fix broken date filter to work correctly with the new year range values.
- #338237 Replace 'local_offset_field' with 'offset_field' in date handler.
- #338237 Fix broken date browser which never got updated to move date field info to $view->date_info.
- Make sure node edit form uses the right timezone for the database and field type.
- Fix logic error in date_convert when using timestamp with timezone other than UTC.
- #336255 Fix a few remaining places where timezone=none was not being handled correctly, and add update to clean up incorrect values stored in database.
- Eliminate date_timezone_convert() which wasn't working correctly to adjust dates without timezones and get rid of hard-coded 'UTC' for database values in favor of consistently using date_get_timezone_db().
- Date sql handler wasn't properly setting db timezone to UTC for mysql databases.
- Add a helper function date_field_default_values() that can be used to create new date fields in profiles and custom code.
- Add better separation to multiple date fields in the view admin summaries for date arguments and filters.
- #337764 Date filter wasn't correctly setting view date info, and also needs a year limit.
- #337301 Add hook_schema_alter to record addition of timezone_name to users table.
- #336255 Add new helper functions to make work of other modules, like Signup, easier. One helper to create a date handler for a field and another to do make date math easier.
Version 6.2-rc5
=================
- #309155 Get rid of all translation during date_repeat calculations, it was causing timeouts with locale module.
- Rename values added to views to avoid name clashes and keep all date values together.
- #336109 Add handling for ordinals to custom date formatting.
- #265076, #324794 Revert attempt to use strtotime to 'guess' text input since it too often ends up silently turning into 'now'.
- #303951 Relax requirements for text date input to allow single digit months and days.
- #309617 Make DATE_REGEX_LOOSE a bit looser so the @ argument will work right for single digit months and days.
- #333831 Make sure Date Repeat widgets are not made available if the Date Repeat module is disabled, and add hooks to mark repeating widgets active and inactive when the module is enabled and disabled.
- #332806 Make sure that Date Popup widgets are marked active when the module is enabled.
- Adapt processing to make sure only selected dates display in nodes, views, and calendars.
- Add Views field handler for date that can be smarter about grouping date values in views.
- Do a little more work on clarifying the repeat descriptions.
- Fix PHP4 timezone problem that didn't save timezone changes correctly in rare cases when refreshing zones using devel.
- Add 'UNTIL' to the RRULE creation function so it can be used by the API and date_content_generate.
- Update date_content_generate to find more matches in a more limited period.
- Make sure repeating date UNTIL date is properly adjusted for timezone.
- #296529 Update repeating date computations to properly adjust the UNTIL date to the right time and timezone so the end date gets picked up correctly.
- #319452 Fix bug in PHP4 calculation of +/-1 week when using Monday as the first day of the week.
- #319452 Make sure repeating date calculations like 'the 5th Sunday' don't prematurely jump to the following period.
- Fix bug in PHP4 calculation of date_date_set() that was getting the day wrong.
- Another tweak to the repeating date theme to show only the first date in nodes, but show the current date in views.
- #329102 Fix mistake in README.txt.
- Set up hook_content_generate to creating random repeating dates as well as regular dates, useful to help debug repeat problems.
- #322845 Repeating dates with deltas greater than zero were showing no date in Views.
- #315009 Rework the generation of dates to work right now that the Content module handles repeats.
- #317057 Make sure existing optional date with empty value doesn't get reset to current date.
- Fix incorrect handling of empty values for multiple value fields, make sure they remain empty.
- Add more checks in PHP4 functions to return usable values when we don't have complete timezone information in the PHP4 wrapper.
- Add helper function to convert input values based on field type.
- Fix timezone caching -- it was resetting when it didn't need to, consuming resources.
- Found a better way to generate random timezones using mt_rand() instead of rand() to avoid getting lots of duplicates.
- Found some timezones that can't be used in PHP4 because we don't have offset information, so remove them from timezone lists.
- Fix error in PHP4 dst switch date for North America.
- Clean up admin summary names for the date argument and date filter.
- #327506 Detect and trap bad timezones in ical import so they don't create errors.
- Fix broken SQL for week arguments and filters, we can't use DATE_FORMAT(), must look for week start and end.
- Default the block_identifier to NULL, not 'mini'.
- Make sure the date filter as well as the date argument can use both the OR and AND methods of combining dates.
- Fix undefined index error for 'step' in date_copy.
- Remove ical import from Date Copy and add instructions on how to do it using Feed API to Advanced help.
- Add a date_embed_view() function to provide a way to embed views and have their navigation move either together or separately, and document it in Advanced help.
- #326945 Remove callbacks in date_field hook() to match CCK.
- Get the links in embedded views and blocks to work correctly using a helper function date_real_url().
- Fix undefined variable in date_ical.inc.
- Set view->date fields when using date filters as well as date arguments.
- Fix Date Views argument handling of summary views, and switch to comparing formatted values instead of complete dates so year arguments will work better.
- Make sure 'time ago' formats will work in calendar by adding more separation into the format_interval formatter.
- #240156 Workaround conflict between Event and Date by using #process on timezone forms to override Event module handling.
- Keep year out of mini navigation title, and make sure it is a link to the view.
- #323265 Add year as well as month to Date browser & calendar titles.
- #174580 Add min/max year setting to Date argument and set Page Not Found outside that range.
- #174580 Add rel=nofollow to back/next links.
- #322649 Add escaping for %s in date format code.
- #322446 Avoid extra space in date theme when there is no timezone.
- #321733 Fix identification of error field for To date that is not greater than From date.
- Fix Views date filter summary, which was using wrong index.
Version 6.2-rc4
=================
- Date argument handler: Add date field in pre_query() instead of query() so it works if the argument is set to the wildcard.
- Don't try to trim date objects in the date ical preprocessor.
- Make a few more fixes to CCK updates in date.install.
- Start on Advanced help for the Date module.
- #317660 Hard code line feeds in ical export to be sure they come through correctly in all systems.
- Don't try to find repeating fields on types without fields.
- #317021 Coder module patch by stella.
- #312996 Break out of endless loop in date_repeat calculation.
- #294851 Date PHP4: Make timezone_identifiers and timezone_abbreviations into statics variable to reduce execution time.
- #294851 Date PHP4: Make timezone_map into a static variable to reduce execution time.
- #314959 Keep repeating dates off the teaser as well as the node.
- #312598 Date PHP4 functions can be off by 1 hour during the day dst changes, add a fix for that.
- Date PHP4: Add new settings area where you can choose whether to use the faster native timezone adjustments for current dates.
- #316392 Hard code the date_api_views_clear() code to prevent update errors.
- #315955 Translation cleanup.
- Add new repeat selection widgets to widgets that can set increments and date parts.
- #304813 Use new recommended method of testing CCK updates using content_check_update().
- #307544 Get rid of extra line feed at end of ical description.
- #304370 Fix typo in popup css.
- #265076 Add some fallback handling for text input that can accept missing time.
- #308353 Long month name translations again, I applied the fix to the wrong function.
- #313447 Throw an error on required dates that have no values filled out.
- #312539 Improvements in translation text.
- #296529 Remove text saying UNTIL date is not included in results, it should be.
- #296529 Make sure UNTIL date takes time into account to avoid missing final date.
- #312974 Add missing closing span in date theme.
- Clean out date displays in Views tables when uninstalled.
- Fix undefined indexes in Date repeat widget logic.
- Add in special widgets for repeating dates so normal widgets can use the regular Content module multiple values handling.
- #309196, #310728 Get rid of attempt to simplify the navigation query, it is too easily broken.
Version 6.2-rc3
=================
- #305376, #303999 Change method of clearing theme registry to avoid errors other themes during update.php.
- #308353 Use long-month-name protocol for translations.
- Eliminate query for navigation display.
- #306404 Make sure update 6001 returns an array.
- Default Date Browser view should not have a content type filter in it.
- Add some validation to date repeat logic to avoid trying to process invalid values.
- #303999 Add function and update to clear out theme registry and all the views caches to pick up changes in Views definitions.
- #304631 Fix validation check for required field that was always triggered.
- #304762 Add missing folders for translations.
- #198502 Make sure updates abort if Content module hasn't yet been updated.
- Update to new Views2 API. Now requires latest versions of Views and CCK, and files have been re-arranged.
- Move timezone handling logic to Date API so we can use it in Calendar, too.
- Rework the date_id formatting to use the new #delta value being passed from CCK.
- #196468 Add link to add a date field to a content type in Date Copy, uses new CCK feature to import from file.
- The timezone translation names in the install file must use underscores instead of spaces.
- #302351 Add timezone info to node_load() and adjust tokens to set the right timezone.
- Replace all include_once() with require_once().
- #276270 Add missing break in date_convert() switch for ICAL.
- #301385 Change hook_requirements() to only set message in runtime to avoid install profile errors.
- #299112 Adjust date_convert() to adapt to am/pm when data source is an array.
- #299112 Don't change 12 hour time to 24 hour time until after validation.
- Make sure date_week() trims input value so you can use it with a regular datetime value.
- Change error messages for ical imports to watchdog messages.
- #294185 Add function for cross-database test of non_null values using COALESCE.
- #300319 Move apostrophe in PostgreSQL offset code.
- #299594 Alter date_api_sql to handle MYSQL versions prior to 4.1.1.
- #297733 Make more kinds of custom formats display correctly.
- Change date description for empty 'to' dates to only be used on date_select, the only place it will work.
- Clean up Date Popup validation.
- #275919, #267195 Clean up date_select validation:
add field name to from and to date so the name can be used in error messages,
combine error messages for field parts into a single message,
remove #options from text elements so we don't get meaningless core message about an invalid option,
set error on whole element so error fields will get outlined.
- Fix weights for timezone and repeat form to always keep them below the date.
- #290212 To date with 'same' default value wasn't picking up blank value when needed.
- #290652 Update jQuery datepicker to development version to pick up fix for duplicated dates when dst changes.
- #298456 Add warnings to field settings page about changes that could result in loss of data.
- #295095 Fix access control on repeating dates tab so it only shows on repeat nodes.
- #295860 Fix flawed updates that were not resetting jscalendar widgets correctly.
- #298158 Change drupal_get_path() to drupal_load() to be sure content module gets included correctly in install file.
- #296409 Fix the hidden repeat date logic so repeating dates are only hidden on the node.
Version 6.2-rc2
=================
- #292617 Make sure the Date Repeat form works properly with all possible date widgets.
- #273656 Make the Date Repeat form use the same widget the regular date form uses.
- #240156 Update Event module timezone values if the Date module is handling the timezone form.
- #296051 Fix error in computation of last day of calendar month.
- Clean up some invalid combinations like using 'DAILY' with an option like 'First Tuesday'.
- #284557 Fix errors saving repeating date when no repeat options were selected.
- #295095 Change the repeating dates theme to show only the start and rule on the node and move the list of repeating dates to a separate tab.
Version 6.2-rc1
=================
- Fix Date Popup process that was ignoring $edit values.
- #289215 Shorten the size of Date Popup date box and allow the API to control the size.
- #291882 Do some tweaking of the Date Popup css to be sure backgrounds are hidden and values are overridden.
- #292945 See if there is an existing validation array before adding validation.
- #293685 Add missing $form_state in form_set_value() call in date_repeat.inc.
- Fix broken logic in comparison of min and max to from and to dates in the date argument.
- #272551 Remove weight for repeat element, it isn't needed and doesn't work right when weight is negative.
- #283392 Make sure widget description shows up with or without 'to' date.
- #292680 Make sure themes don't add padding to navigation h3 causing misalignment.
- #292602 Make sure dates without times do not get 'All day' added.
- #292436 Fix critical typo in new date_all_day function and add test to date_formatter_process() to keep empty value from wiping out a good one.
- Check for date values before using new all day theme.
- Add 'All day' themes for use in nodes and calendar psuedo nodes.
- #290826 Fix logic error that was missing date ranges that start before the current period by changing 'AND' to 'OR' in argument filter.
Version 6.2-beta4
=================
- #270318 Get date browser and calendar navigation working correctly when used in a block.
- Add new DATE_FORMAT_DATE for date-only format that is used throughout the calendar.
- Fix validation code that checks if arguments and filters have date fields selected, need to array_filter() the selections.
- #269569 Override Views default method of combining tables when adding two fields from the same table, needed to avoid broken queries.
- Add validation for Date Browser to be sure only one date argument is used in the view.
- Rework the install file so variables can be corrected and dependent modules enabled by disabling and re-enabling Date API.
- #280863 Make sure Date Timezone and Date PHP4 can be uninstalled by keeping module_enable() in the install.
- Rework of date filter to provide a choice of a widget to use for the filter form and an option to set the default value to a set date or something like 'now +90 days'. You must delete any previously-created date filters and re-create them to pick up the new parameters.
- Make sure PHP4 date_modify() does not care if there is a space between the number and the date part.
- Make sure we don't use mktime() in compare_date() function, won't work reliably in PHP4.
- Make sure PHP4 wrapper code doesn't try to use date() on values outside the 32 bit signed range.
- Get the url into the theme processing.
- Remove old D5 themes, this is now handled by the Date API date browser attachment.
- #286864 Fix wrong RRULE value being passed to validator and add trim() to get repeating dates working again.
- #286864 Need a different fix for timezone API since this fix broke Date Repeat.
- #283107 Evaluate date and time parts separately since we can't know or care how they're combined in the complete date format.
- #283107 Date Popup timepicker formats need adjustment before using date_convert_from_custom().
- Exception dates were getting lost when parsing the RRULE.
- Increase size of Date Popup input box to accept longer formats that the new datepicker allows.
- #287325 Change the default date type in the date handler to DATE_DATETIME.
- Add test for jQueryUI and load datepicker from there if it exists.
- Update to latest versions of jQuery datepicker and timeentry.
- Hide image div in Date Popup timeentry css to keep FF from displaying a 'helpful' dropdown.
- Move date_format_options() to Date API so other code can use it.
- #279247 Make sure date_timezone element works no matter what the parent element name is.
- Fix undefined index in PHP4 date_datetime2timestamp() function.
- #265076 Avoid use of strtotime and use date_create instead as date_text validator.
- #277771 Fix date_gmmktime() computation in PHP4 date handling to avoid erratic daylight savings time problem.
- Fix computation of 'now' in PHP4 date_create() function.
- Fix some inconsistencies in the expectation of whether date_fuzzy_datetime returns a datetime or ISO value.
- Don't use date_make_date() in date_convert() to avoid cyclical references.
- Set a warning message for invalid non-required Date popup values instead of silently setting to NULL.
- #244025 Fix jquery calendar so it works correctly with multiple value fields.
- #269569 Make sure more than one date filter will work on the same view.
- Add a template for the Date Views filter form.
- Check for invalid inputs to the date handler argument handling.
- Move the class configuration to the theme so it is easier for themers.
- Remove code previously commented out that forced required value to current date, seems to be working right.
- Adapt date elements so they will work correctly as Views 2 widgets.
- Move #validate parameters to element processing step instead of hook_element() to help ensure they don't get wiped out.
- Make sure partial dates, like year-only, get the right range computed for them.
- Don't user format_date() and strtotime() to create argument titles and links.
- #282408 Make sure dates only float when there are two of them by adding #date_float parameter.
- #281623 Switch old jscalendar widgets to use date_text instead of date_popup since date_popup may not be available.
- Make sure empty time does not get value on submit.
- Format interval theme added 'ago' twice to past events.
- Need a space between the date and the timezone name in the Date theme.
- Get repeating dates working.
- #280899, #273344 Make sure Date Popup doesn't inject the current date into empty fields.
- Fix undefined index for timezone field.
- Fix computation of year range in date_content_generate.
- #275797 Remove invalid and unneeded css display:relative.
- Fix inconsistencies in handling of 'none' and 'user' timezones in Date and Calendar.
- Add static variable to PHP4 timezone offset handling to reduce number of database queries.
- #280041 Fix typo in postgres switch code in date_api_sql.inc.
- #264749 Fix typo in date_ical_date which unset the date.
- #277420 Add missing format information for minutes and seconds to date_sql_handler.
- Make sure the timezone always goes on its own line now that date elements float.
- #279932 Remove length from integer rows so they don't create errors in postgres.
- #270316 Fix postgres timezone adjustment.
- #279051 Fix postgres timezone test.
- Add week handling and granularity tests to date_sql_handler().
- Set date prefix on date navigation css classes.
- Fix PHP4 handler for date_timezone_set() which was incorrectly altering the timestamp value.
- Fix computation of 'N' in PHP4 date_format() wrapper to get date_week() working right in PHP4.
- #274403 Fix typo in date_hours().
- #269834 Add timezone adjustment to PHP4 date_format() wrapper.
- #272110 Alter css so that from/to dates can float next to each other.
- #275490 Remove orphaned punctuation at beginning of string in date_limit_format().
- #277549 Make sure no repeats are created if no start date was set.
- #272523 Fix error in PHP4 wrapper code that kept date_modify from working reliably.
- #273344 Make sure incomplete Date Popup values get converted into complete dates.
- #274882 Fix typo using $field instead of $form_values.
- #276544 Rework system for handling timezone in date theme.
- Add code to adjust NOW() by a number of seconds to allow NOW() to be the beginning of the day.
- Rework PHP4 date handling to avoid any use of date() or mktime(), which may inject an incorrect timezone offset.
- Get rid of the adodb method of computing the gmt offset for PHP4 and use the date_offset_get() computation.
- Fix use of wrong value for timezone in repeating dates.
- Remove as many dependencies as possible from install files and auto-enable necessary modules.
- #270267 Remove timezone_identifiers_list() in the install file from the global space.
- Date Popup was not handling empty non-required values correctly and they were defaulting to current date.
- #273727 Small fixes to field settings validation.
- #248338 and #254819 clean up date_limit_format() to better handle punctuation and escaped letters.
- The jQuery calendar requires year, month, and day or it won't work, add validation for that in the settings.
- #270358 Fix handling of year-only or month-only text fields to save the right values.
- Remove unneeded hook_form_alter() used to fix radio values for granularity.
- Make sure granularity is in correct format before setting value in field settings form.
- #244025 Didn't get the right code into the original commit for the single jquery class.
Still have the problem that it won't work with AHAH add more.
- #273130 Conform to new Drupal 6 method of indicating long and short month names.
- Clean up Date popup validation.
- Add templates for ical vevent and valarm.
- Fix undefined indexes in date_repeat_calc.inc.
- Change method of showing multiple values with timezones on forms and show them in every instance.
This is to simplify the processing and avoid errors when trying to apply the timezone in the
first item to all other items.
- #272597 Remove php tag from timezone.js.
- #272551 Add option to expand or collapse repeating date options.
- #261610 Prepare for a jQuery popup in the calendar by adding a unique id to the 'node'
and altering the date theme to surpress all other dates if $node->date_id is set.
- #232959 Get rid of unwanted scrollbars in date fieldsets using trick discovered by threexk.
- Clean up install file included files and include file paths, and auto-enable date_timezone and date_php4.
- Add a is_string() test to the date_is_valid() function.
- The timezone element can sometimes be a nested value.
- Date_fuzzy_date was not handling input from date_popup or date_text in the right way.
- #260611 Replace substr with drupal_substr where used to get month and day abbreviations.
- The granularity options were not saved correctly by CCK when provided as checkboxes, change them back to a select widget and fix the bad values in an update.
- Have the date_text element handle its own format description instead of doing it in the Date module.
- #270304 Timezone was not getting saved.
- Empty values were getting saved.
- #257353 No need for format description when using select widget.
- #270267 Auto-enable the Date PHP4 module when needed.
- #269569 Get the Date Views filter working.
- #270626 Fix documentation errors that use create_date() instead of date_create().
Version 6.2-beta3
=================
- #269569 Get new generic date filter working again.
- Add a default view for the Date Browser.
- Fix undefined indexes and add ability to override style in the Date Browser navigation attachment.
- Add RRULE to ical template. Make sure all event values get added into the ical feed.
- Get rid of date_ical_export(). The ical export code has been moved into a template and pre_processor.
- Make Date API changes needed to get iCal export working in Calendar.
- #251830 Replace D5 function name with D6 function name, content_alter_db_field() is D5 only.
Version 6.2 beta2
=================
- Fix computation of week range computed from views arguments.
- Now that a current date argument has been added to Views, remove the current date plugin from the Date API views handling.
- #263377 Re-fix patch that I broke in later commit.
- Comment out RRULE temporarily until repeating rules are fixed.
- Omit the timezone from the process to store the current date parts in the form.
- Change expected form values to match latest changes in CCK admin form.
- #261631 No longer using $append, so get rid of it.
- More work to ensure all the right timezone files are available in installations and updates.
- Add theme folders to 6.2 branch.
- #262272 Make sure $account is set before trying to use it in hook_user.
- #263078 Fix error in setting up db session info that was triggering the MYSQL code for POSTGRES.
- Move lots of similar date handling and navigation code out of Date and Calendar and into the Date API where we can use the same code everywhere.
- Add a flexible date argument to the API that will work on any Views date field and take any ISO date argument.
- Add an argument default option to set a missing date argument to the current date that will work on any date field.
- Add a date back/next navigation attachment that works with the date argument and which can be attached to any view.
- Adapt the Calendar module to share this code instead of creating its own.
- Add calendar week calculations to the Date API so we can move them out of Calendar module to be able to use them in any date application.
- Fix critical typo in date_get_timezone() that set the timezone to the site timezone even if it isn't right.
- #257764 Replace hard-coded field and instance tablenames with the new content module function.
- #130689 Move include code in the install file into a function and out of the global scope.
- Fixes needed to display the timezone when that format is selected, and to pull the timezone out of format strings.
- Date with timezone wasn't displaying the timezone selector.
- #261628 fix typo in #process.
- Change to sql handling from latest updates to the Calendar module.
Version 6.2 beta
================
- #255911 node_submit() is not necessary for data integrity check.
- Rename date.views.inc and change handling to new Views auto-include method.
- Fix for critical flaw in the new timezone list handling that was creating an invalid array.
- #257874 and #187599 provide a way to generate field content for the devel module.
- Bad typo in date column type.
- Fix install errors.
- #255166 fix copy/paste error in date_token function name.
- Working in a new sql handler class so we can use the same class in both.
- Update date copy to convert either version 1 or version 2 Event nodes.
- Add missing strtotime validation and format help text to textfield date and clarify that it's really a custom input format (which uses strtotime if the custom input fails).
- Change method of getting translations for all timezones into the install file.
- #256079 make translation of abbreviations easier.
- #254930 fix parse error.
- #238794, #254851 fix call-time pass-by-reference error.
- #256078 Fix typo in popup calendar.
- #255739 Fix backwards validation message when testing that the To date is greater than the From date.
- Make sure anonymous users don't see timezone message.
- Prepare for proposed core timezone handling from #11077 by getting rid of deprecated timezones and adding in js timezone name detection.
- Rework default timezone handling to force a valid date even when timezones are not set to get rid of numerous installation and operational errors caused by invalid date objects.
- Update site timezone offset during cron and user offset during login to make sure that modules that rely on the timezone offset have current information to process when the offset changes.
- #251511 Rename function to avoid clash with Diff module.
- Rework the date filter and argument -- simplify some of the code but still keep as much flexibility as possible. You can set up the filter to provide either a date or offset selector (or both), you can set the granularity of the date parts, and any part can be set to all all values, the current time, or a specific value for either a single date or a from/to combination.
- #250813 Make sure Views handlers are initiated.
- #244708 menu code cleanup.
- Clean up timezone handling in date repeat logic.
- #234021 fix date_repeat timestamp errors.
- #234102 It turns out that we must always force use of the lower level date functions because regular strtotime and mktime can be off by one hour during daylight savings time.
- #234073 Provide a way to make required textfield start out with blank values.
- #239900, #249399 Limit available formats for Date Popup to available formats and hide custom format code which won't work right here.
- Can't use date_make_date within date_part_extract without setting up potential circular reference.
- Format_interval logic was backwards.
- #236889 Make sure date_select validation only checks required granularity.
- #234073 Provide a way to make a required date start out with blank values.
- #247749 don't use date popup on system date form, too many potential conflicts.
- #234360 fix date_server_zone_adj() function.
- #242433 adjust timestamps to datetime when creating repeating dates.
- #233415 separate ical retrieval and parser so other modules like feedapi can use the parser alone.
- #240980 coder compliance fixups.
- #225738 Add special case handling for year only or year and month only ISO dates.
- #244025 make the jquery calendar code more efficient.

View file

@ -0,0 +1,71 @@
==================================================================================
Date API Installation instructions:
==================================================================================
1) If you have an earlier version of the Date module on your system, empty the
date folder out completely. The files in version 2 have different names and are
located in different places.
2) Download the whole package of files from http://drupal.org/project/date.
3) Upload the date files to the modules directory. The package includes files
needed by the Date API, and optional modules to to create CCK date fields.
4) Go to admin/build/modules and enable the needed modules from the Date/Time group.
You should end up with a structure like:
/drupal/sites/all/modules/date/date_api.info
/drupal/sites/all/modules/date/date_api.install
/drupal/sites/all/modules/date/date_api.module
...
/drupal/sites/all/modules/date/date/date.info
/drupal/sites/all/modules/date/date/date.install
/drupal/sites/all/modules/date/date/date.module
...
/drupal/sites/all/modules/date/date_copy/date_copy.info
/drupal/sites/all/modules/date/date_copy/date_copy.module
...
/drupal/sites/all/modules/date/date_php4/date_php4.inc
/drupal/sites/all/modules/date/date_php4/date_php4_lib.inc
...
==================================================================================
Older PHP versions
==================================================================================
If you are using PHP 4 or PHP 5.0 or 5.1, native date handling won't work right.
Install the Date_PHP4 module to enable wrapper functions so this code will work
in old PHP versions.
==================================================================================
Enable Date Timezone
==================================================================================
In most cases, you should enable the Date Timezone module any time you use the
Date API to be able to set the site and user timezone names. It is not enabled by
default in case another module is setting timezone names in the database.
Once you have enabled it, go to admin/settings/date-time and set the default
site timezone name. If you are using user timezones, go to your account settings
and set up your own timezone name.
==================================================================================
Install CCK Date Fields:
==================================================================================
1) The CCK date field is included in the Date files at http://drupal.org/project/date.
2) Go to admin/build/modules and enable the Date module. Be sure that the Date API module,
the Date Timezone module, and the Content module are also installed.
3) Go to admin/content/types to view cck content types and edit a content type.
4) Make sure the default timezone name has been set at admin/settings/date-time.
5) While viewing a content type, select the option to add a new field from the tabs at
the top of the page. Several options for date fields should be visible.
==================================================================================
More documentation is available at http://drupal.org/node/92460.
==================================================================================

View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -0,0 +1,307 @@
INFORMATION FOR DEVELOPERS
Once the Date API is installed, all functions in the API are available to be used
anywhere by any module. If the Date Timezone module is installed, the system site
timezone selector and the user timezone selectors are overwritten to allow the
selection of timezone names instead of offsets. Proper timezone conversion
requires knowledge of those timezone names, something that is not currently
available in Drupal core, and the change in selectors makes it possible to track it.
In most cases, you should enable the Date Timezone module any time you use the
Date API to be able to set the site and user timezone names. It is not enabled by
default in case another module is setting timezone names in the database.
The API uses the PHP 5.2 date functions to create and manipulate dates, and
contains an option module that will emulate those functions in earlier versions
of PHP.
If you are using PHP 4 or PHP 5.0 or 5.1, native date handling won't work right.
Install the Date_PHP4 module to enable wrapper functions so this code will work
in old PHP versions.
Example, the following will create a date for the local value in one
timezone, adjust it to a different timezone, then return the offset in seconds
in the new timezone for the input date; The offset will be adjusted for both
the timezone difference and daylight savings time, if necessary:
$date = date_create('2007-03-11 02:00:00', timezone_open('America/Chicago'));
$chicago_time = date_format($date, 'Y-m-d H:i');
print 'At '. $chicago_time .' in Chicago, the timezone offset in seconds
was '. date_offset_get($date);
date_timezone_set($date, timezone_open('Europe/Berlin');
$berlin_time = date_format($date, 'Y-m-d H:i');
print 'It was '. $berlin_time .' in Berlin when it
was '. $chicago_time .' in Chicago.';
print 'At that time in Berlin, the timezone offset in seconds was
'. date_offset_get($date);
A helper function is available, date_make_date($string, $timezone, $type),
where $string is a unixtimestamp, an ISO date, or a string like YYYY-MM-DD HH:MM:SS,
$timezone is the name of the timezone this date is in, and $type is the type
of date it is (DATE_UNIX, DATE_ISO, or DATE_DATETIME). It create and return
a date object set to the right date and timezone.
Simpletest tests for these functions are included in the package.
Available functions include the following (more documentation is provided in
the files):
============================================================================
Date PHP4 Module
============================================================================
PHP 4 substitutions for the PHP 5 date functions are supplied. Use the PHP 5
functions in your code as they would normally be used and the PHP 4
alternatives will be automatically be substituted in when needed.
You cannot do everything with these functions that can be done in PHP 5, but
you can create dates, find timezone offsets, and format the results.
Timezone handling uses native PHP 5 functions when available and degrades
automatically for PHP 4 to use substitutions like those
provided in previous versions of the Date and Event modules.
Read the doxygen documentation in this module for more information
about using the functions in ways that will work in PHP 4.
Simpletest tests for the PHP 4 equivalent functions are included in the package.
The following functions are emulated in PHP4:
date_create()
date_date_set()
date_format()
date_offset_get()
date_timezone_set()
timezone_abbreviations_list()
timezone_identifiers_list()
timezone_offset_get()
timezone_open()
============================================================================
Preconfigured arrays
============================================================================
Both translated and untranslated values are available. The date_week_days_ordered()
function will shift an array of week day names so it starts with the site's
first day of the week, otherwise the weekday names start with Sunday as the first
value, the expected order for many php and sql functions.
date_month_names();
date_month_names_abbr();
date_month_names_untranslated();
date_week_days();
date_week_days_abbr();
date_week_days_untranslated();
date_week_days_ordered();
date_years();
date_hours();
date_minutes();
date_seconds();
date_timezone_names();
date_ampm();
============================================================================
Miscellaneous date manipulation functions
============================================================================
Pre-defined constants and functions that will handle pre-1970 and post-2038
dates in both PHP 4 and PHP 5, in any OS. Dates can be converted from one
type to another and date parts can be extracted from any date type.
DATE_DATETIME
DATE_ISO
DATE_UNIX
DATE_ARRAY
DATE_OBJECT
DATE_ICAL
date_convert()
date_is_valid();
date_part_is_valid();
date_part_extract();
============================================================================
Date calculation and navigation
============================================================================
date_difference() will find the time difference between any two days, measured
in seconds, minutes, hours, days, months, weeks, or years.
date_days_in_month();
date_days_in_year();
date_weeks_in_year();
date_last_day_of_month();
date_day_of_week();
date_day_of_week_name();
date_difference();
============================================================================
Date regex and format helpers
============================================================================
Pre-defined constants, an array of date format strings and their
equivalent regex strings.
DATE_REGEX_LOOSE is a very loose regex that will pull date parts out
of an ISO date with or without separators, using either 'T' or a space
to separate date and time, and with or without time.
date_format_date() is similar to format_date(), except it takes a
date object instead of a timestamp as the first parameter.
DATE_FORMAT_ISO
DATE_FORMAT_DATETIME
DATE_FORMAT_UNIX
DATE_FORMAT_ICAL
DATE_REGEX_ISO
DATE_REGEX_DATETIME
DATE_REGEX_LOOSE
date_format_date();
date_t()
date_short_formats();
date_medium_formats();
date_long_formats();
date_format_patterns();
============================================================================
Standardized ical parser and creator
============================================================================
The iCal parser is found in date_api_ical.inc, which is not included by default.
Include that file if you want to use these functions:
Complete rewrite of ical imports to parse vevents, vlocations, valarms,
and all kinds of timezone options and repeat rules for ical imports.
The function now sticks to parsing the ical into an array that can be used
in various ways. It no longer trys to convert timezones while parsing,
instead a date_ical_date_format() function is provided that can be used to
convert from the ical timezone to whatever timezone is desired in the
results. Repeat rules are parsed into an array which other modules can
manipulate however they like to create additional events from the results.
date_ical_export();
date_ical_import();
date_ical_date_format();
============================================================================
Helpers for portable date SQL
============================================================================
The SQL functions are found in date_api_sql.inc, which is not included by default.
Include that file if you want to use these functions:
date_sql();
date_server_zone_adj();
date_sql_concat();
date_sql_pad();
============================================================================
Date forms and validators
============================================================================
Reusable, configurable, self-validating FAPI date elements are found in
date_api_elements.inc, which is not included by default. Include it
if you want to use these elements. To use them, create a form element
and set the '#type' to one of the following:
date_select
The date_select element will create a collection of form elements, with a
separate select or textfield for each date part. The whole collection will
get re-formatted back into a date value of the requested type during validation.
date_text
The date_text element will create a textfield that can contain a whole
date or any part of a date as text. The user input value will be re-formatted
back into a date value of the requested type during validation.
date_timezone
The date_timezone element will create a drop-down selector to pick a
timezone name.
The custom date elements require a few other pieces of information to work
correctly, like #date_format and #date_type. See the internal documentation
for more information.
============================================================================
Date Popup Module
============================================================================
A new module is included in the package that will enable a popup jQuery
calendar date picker and timepicker in date and time fields.
It is implemented as a custom form element, so set '#type' to 'date_popup'
to use this element. See the internal documentation for more information.
============================================================================
Date Repeat API
============================================================================
An API for repeating dates is available if installed. It can be used by
other modules to create a form element that will allow users to select
repeat rules and store those selections in an iCal RRULE string, and a
calculation function that will parse the RRULE and return an array of dates
that match those rules. The API is implemented in the Date module as a
new date widget if the Date Repeat API is installed.
============================================================================
Install file for dependent modules
============================================================================
The following code is an example of what should go in the .install file for
any module that uses the new Date API. This is needed to be sure the system
is not using an earlier version of the API that didn't include all these new
features. Testing for version '5.2' will pick up any version on or after the
change to the new API.
/**
* Implementation of hook_requirements().
*/
function calendar_requirements($phase) {
$requirements = array();
$t = get_t();
// This is the minimum required version for the Date API so that it will
work with this module.
$required_version = 5.2;
// Make sure the matching version of date_api is installed.
// Use info instead of an error at install time since the problem may
// just be that they were installed in the wrong order.
switch ($phase) {
case 'runtime':
if (variable_get('date_api_version', 0) < $required_version) {
$requirements['calendar_api_version'] = array(
'title' => $t('Calendar requirements'),
'value' => $t('The Calendar module requires a more current version
of the Date API. Please check for a newer version.'),
'severity' => REQUIREMENT_ERROR,
);
}
break;
case 'install':
if (variable_get('date_api_version', 0) < $required_version) {
$requirements['calendar_api_version'] = array(
'title' => $t('Calendar requirements'),
'value' => $t('The Calendar module requires the latest version
of the Date API, be sure you are installing the latest versions
of both modules.'),
'severity' => REQUIREMENT_INFO,
);
}
break;
}
return $requirements;
}
/**
* Implementation of hook_install().
*/
function calendar_install() {
// Make sure this module loads after date_api.
db_query("UPDATE {system} SET weight = 1 WHERE name = 'calendar'");
}
/**
* Implementation of hook_update().
*/
function calendar_update_5000() {
$ret = array();
$ret[] = update_sql("UPDATE {system} SET weight = 1 WHERE name = 'calendar'");
return $ret;
}

View file

@ -0,0 +1,33 @@
.container-inline-date {
margin-left: 0.5em;
margin-right: 0;
}
.container-inline-date .form-item .form-item {
float: right;
}
.container-inline-date .form-item input,
.container-inline-date .form-item select,
.container-inline-date .form-item option {
margin-left: 5px;
margin-right: 0;
}
.container-inline-date .date-spacer {
margin-left: 0;
margin-right: -5px;
}
.date-nav div.date-prev {
text-align: right;
float: right;
}
.date-nav div.date-next {
text-align: left;
float: right;
}
.date-nav div.date-heading {
float: right;
}

View file

@ -0,0 +1,206 @@
/* Force from/to dates to float using inline-block, where it works, otherwise inline. */
.container-inline-date {
width:auto;
clear:both;
display: inline-block;
vertical-align:top;
margin-right: 0.5em; /* LTR */
}
.container-inline-date .form-item {
float:none;
padding:0;
margin:0;
}
.container-inline-date .form-item .form-item {
float: left; /* LTR */
}
.container-inline-date .form-item,
.container-inline-date .form-item input {
width:auto;
}
.container-inline-date .description {
clear: both;
}
.container-inline-date .form-item input,
.container-inline-date .form-item select,
.container-inline-date .form-item option {
margin-right: 5px; /* LTR */
}
.container-inline-date .date-spacer {
margin-left: -5px; /* LTR */
}
.views-right-60 .container-inline-date div {
padding:0;
margin:0;
}
.container-inline-date .date-timezone .form-item {
float:none;
width:auto;
clear:both;
}
/* Fixes for date popup css so it will behave in Drupal */
#calendar_div, #calendar_div td, #calendar_div th {
margin:0;
padding:0;
}
#calendar_div,
.calendar_control,
.calendar_links,
.calendar_header,
.calendar {
width: 185px;
border-collapse: separate;
margin: 0;
}
.calendar td {
padding: 0;
}
/* formatting for from/to dates in nodes and views */
span.date-display-single {
}
span.date-display-start {
}
span.date-display-end {
}
span.date-display-separator {
}
.date-repeat-input {
float: left; /* LTR */
width:auto;
margin-right: 5px; /* LTR */
}
.date-repeat-input select {
min-width:7em;
}
.date-repeat fieldset {
clear:both;
float:none;
}
.date-views-filter-wrapper {
min-width:250px;
}
.date-views-filter input {
float: left !important; /* LTR */
margin-right: 2px !important; /* LTR */
padding:0 !important;
width:12em;
min-width:12em;
}
.date-nav {
width:100%;
}
.date-nav div.date-prev {
text-align: left; /* LTR */
width:24%;
float: left; /* LTR */
}
.date-nav div.date-next {
text-align: right; /* LTR */
width:24%;
float: right; /* LTR */
}
.date-nav div.date-heading {
text-align:center;
width:50%;
float: left; /* LTR */
}
.date-nav div.date-heading h3 {
margin:0;
padding:0;
}
.date-clear {
float:none;
clear:both;
display:block;
}
.date-clear-block {
float:none;
width:auto;
clear:both;
}
/*
** Markup free clearing that fixes unwanted scrollbars
** @see http://drupal.org/node/232959
*/
.date-clear-block:after {
content: " ";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.date-clear-block {
display: inline-block;
}
/* Hides from IE-mac \*/
* html .date-clear-block {
height: 1%;
}
.date-clear-block {
display: block;
}
/* End hide from IE-mac */
.date-container .date-format-delete {
margin-top: 1.8em;
margin-left: 1.5em;
float: left;
}
.date-container .date-format-name {
float: left;
}
.date-container .date-format-type {
float: left;
padding-left: 10px;
}
.date-container .select-container {
clear: left;
float: left;
}
/* Calendar day css */
div.date-calendar-day {
line-height: 1;
width: 40px;
float: left;
margin: 6px 10px 0 0;
background: #F3F3F3;
border-top: 1px solid #eee;
border-left: 1px solid #eee;
border-right: 1px solid #bbb;
border-bottom: 1px solid #bbb;
color: #999;
text-align: center;
font-family: Georgia, Arial, Verdana, sans;
}
div.date-calendar-day span {
display: block;
text-align: center;
}
div.date-calendar-day span.month {
font-size: .9em;
background-color: #B5BEBE;
color: white;
padding: 2px;
text-transform: uppercase;
}
div.date-calendar-day span.day {
font-weight: bold;
font-size: 2em;
}
div.date-calendar-day span.year {
font-size: .9em;
padding: 2px;
}

View file

@ -0,0 +1,13 @@
name = Date
description = Defines CCK date/time fields and widgets.
dependencies[] = content
dependencies[] = date_api
dependencies[] = date_timezone
package = Date/Time
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,457 @@
<?php
/**
* Implementation of hook_install().
* Reset the date caches.
*/
function date_install() {
drupal_load('module', 'content');
content_notify('install', 'date');
}
/**
* Implementation of hook_uninstall().
*/
function date_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'date');
}
/**
* Implementation of hook_enable().
* Reset the calendar caches.
*/
function date_enable() {
drupal_load('module', 'content');
content_notify('enable', 'date');
}
/**
* Implementation of hook_disable().
* Empty the date caches.
*/
function date_disable() {
drupal_load('module', 'content');
content_notify('disable', 'date');
}
function date_update_last_removed() {
return 5210;
}
/**
* Granularity options were not saved correctly by CCK if created using checkboxes.
* Checkboxes have been changed back to a select and we need to fix the values
* stored in the settings.
*
* The bad values would have been stored in the form
* array(
* 'year' => 'year'
* 'month' => 'month',
* 'day' => 0,
* 'hour' => 0,
* 'minute' => 0,
* 'second' => 0,
* );
*
* Good values would have been stored in the form
* array(
* 'year' => 'year',
* 'month' => 'month',
* );
*
* This might or might not have gotten updated in D5.2,
* we need to force it again in D6 just in case.
*
* @return unknown
*/
function date_update_6000() {
include_once(drupal_get_path('module', 'content') .'/content.install');
if ($abort = content_check_update('date')) {
return $abort;
}
drupal_load('module', 'content');
$ret = array();
$result = db_query("SELECT field_name, global_settings from {". content_field_tablename() ."} where type LIKE 'date_%'");
while ($field = db_fetch_array($result)) {
// Change the format to one date_popup can use.
$field_settings = unserialize($field['global_settings']);
$granularity = array_filter($field_settings['granularity']);
$field_settings['granularity'] = $granularity;
db_query("UPDATE {". content_field_tablename() ."} SET global_settings = '%s' WHERE field_name = '%s'", serialize($field_settings), $field['field_name']);
}
content_clear_type_cache();
return $ret;
}
/**
* Get rid of jscalendar popup widget.
* Originally update_5201, but that was broken.
*/
function date_update_6001() {
include_once(drupal_get_path('module', 'content') .'/content.install');
if ($abort = content_check_update('date')) {
return $abort;
}
drupal_load('module', 'content');
$ret = array();
if (db_result(db_query("SELECT COUNT(*) FROM {". content_instance_tablename() ."} WHERE widget_type = 'date_js'"))) {
$replace = module_exists('date_popup') ? 'date_popup' : 'date_text';
$result = db_query("SELECT field_name, type_name, widget_type from {". content_instance_tablename() ."} where widget_type = 'date_js'");
while ($widget = db_fetch_array($result)) {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_type = '$replace' WHERE field_name = '%s' AND type_name = '%s'", $widget['field_name'], $widget['type_name']);
}
drupal_set_message(t('All date fields using the jscalendar widget have been changed to use the text widget instead, since the jscalendar widget is no longer supported. Enable the Date Popup module to make a jQuery popup calendar available and edit the field settings to select it.'));
content_clear_type_cache();
}
return $ret;
}
/**
* Switch to using different widgets for repeating dates so non-repeats
* can use standard Content module multiple values handling.
*/
function date_update_6002() {
include_once(drupal_get_path('module', 'content') .'/content.install');
if ($abort = content_check_update('date')) {
return $abort;
}
drupal_load('module', 'content');
$ret = array();
$types = content_types();
$repeating_fields = array();
foreach ($types as $type) {
foreach ($type['fields'] as $field_name => $field) {
if (!empty($field['repeat'])) {
$repeating_fields[] = $field_name;
}
}
}
if (count($repeating_fields)) {
$replace = array(
'date_select' => 'date_select_repeat',
'date_text' => 'date_text_repeat',
'date_popup' => 'date_popup_repeat',
);
$result = db_query("SELECT * from {". content_instance_tablename() ."} WHERE widget_type IN('date_select', 'date_text', 'date_popup') AND field_name IN('". implode("','", $repeating_fields) ."')");
while ($widget = db_fetch_array($result)) {
db_query("UPDATE {". content_field_tablename() ."} SET multiple = 1 WHERE field_name = '%s'", $widget['field_name']);
db_query("UPDATE {". content_instance_tablename() ."} SET widget_type = '". $replace[$widget['widget_type']] ."' WHERE field_name = '%s' AND type_name = '%s'", $widget['field_name'], $widget['type_name']);
}
drupal_set_message(t('All repeating date fields have been updated to use the new repeating widgets.'));
content_clear_type_cache();
}
return $ret;
}
/**
* Update all repeating date delta values with the RRULE
* so we can use that info in Views when we pull individual items
* out in a query.
*/
function date_update_6003() {
$ret = array();
drupal_load('module', 'content');
$fields = content_fields();
foreach ($fields as $field) {
if (strstr($field['type'], 'date') && !empty($field['repeat'])) {
$db_info = content_database_info($field);
$table = $db_info['table'];
$column = $field['field_name'] .'_rrule';
$result = db_query("SELECT DISTINCT nid, vid, $column FROM {". $table ."} WHERE $column <> ''");
while ($row = db_fetch_array($result)) {
$ret[] = update_sql("UPDATE {". $table ."} SET $column='". $row[$column] ."' WHERE nid=". $row['nid'] ." AND vid=". $row['vid']);
}
}
}
return $ret;
}
/**
* Empty the stored db for timezones that use timezone 'none'.
*/
function date_update_6004() {
$ret = array();
drupal_load('module', 'content');
$fields = content_fields();
foreach ($fields as $field) {
if (strstr($field['type'], 'date') && !empty($field['tz_handling']) && $field['tz_handling'] == 'none') {
$row = db_result(db_query("SELECT global_settings FROM {". content_field_tablename() ."} WHERE field_name='". $field['field_name'] ."'"));
$settings = unserialize($row);
$settings['timezone_db'] = '';
$settings = serialize($settings);
db_query("UPDATE {". content_field_tablename() ."} SET global_settings='%s' WHERE field_name='%s'", $settings, $field['field_name']);
}
}
drupal_set_message(t("The database has been updated to correct the stored timezone for fields using timezone 'none'."));
content_clear_type_cache();
return $ret;
}
function date_update_6005() {
include_once(drupal_get_path('module', 'content') .'/content.install');
if ($abort = content_check_update('date')) {
return $abort;
}
// The new format table won't get built before the system tries to run this update.
// We need to abort and tell the user to re-run it.
// We need a custom abort process because the content_check_update could be OK
// but we still need to update these values.
if ((!db_table_exists('date_format') && !db_table_exists('date_formats')) || !db_table_exists('date_format_types')) {
drupal_set_message(t('Some updates are still pending. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'error', FALSE);
$ret['#abort'] = array('success' => FALSE, 'query' => t('Some updates are still pending.<br/>Please re-run the update script.'));
return $ret;
}
drupal_load('module', 'content');
$ret = array();
$new_map = array(
'short' => 'short',
'medium' => 'medium',
'long' => 'long',
'time' => 'time',
'time_timezone' => 'time_timezone',
'iso' => 'iso',
'timestamp' => 'timestamp',
'ical' => 'ical',
'feed' => 'feed',
);
// Create new custom formats for each of these:
date_install_create_format(NULL, t('Time'), 'time', date_limit_format(variable_get('date_format_short', 'm/d/Y - H:i'), array('hour', 'minute', 'second')));
date_install_create_format(NULL, t('Time with timezone'), 'time_timezone', date_limit_format(variable_get('date_format_short', 'm/d/Y - H:i') .' e', array('hour', 'minute', 'second', 'timezone')));
date_install_create_format(NULL, t('iCal'), 'ical', 'Ymd\THis');
date_install_create_format(NULL, t('Timestamp'), 'timestamp', 'U');
date_install_create_format(NULL, t('Feed'), 'feed', 'D, j M Y H:i:s O');
date_install_create_format(NULL, t('ISO'), 'iso', DATE_FORMAT_ISO);
$result = db_query("SELECT field_name, global_settings from {". content_field_tablename() ."} where type='date' OR type='datestamp' OR type='datetime'");
while ($field = db_fetch_array($result)) {
$field_settings = unserialize($field['global_settings']);
$field_name = $field['field_name'];
$default = !empty($field_settings['output_format_custom']) ? $field_settings['output_format_custom'] : $field_settings['output_format_date'];
$short = !empty($field_settings['output_format_custom_short']) ? $field_settings['output_format_custom_short'] : $field_settings['output_format_date_short'];
$med = !empty($field_settings['output_format_custom_medium']) ? $field_settings['output_format_custom_medium'] : $field_settings['output_format_date_medium'];
$long = !empty($field_settings['output_format_custom_long']) ? $field_settings['output_format_custom_long'] : $field_settings['output_format_date_long'];
$system_short = variable_get('date_format_short', 'm/d/Y - H:i');
$system_med = variable_get('date_format_medium', 'D, m/d/Y - H:i');
$system_long = variable_get('date_format_long', 'l, F j, Y - H:i');
// The only thing we'll keep is the name of a default format type.
$new_setting = 'default_format';
// Create a map of the old and new formats.
$map = $new_map;
// If the field's long, medium, or short formats don't match the system
// values, create custom format types for them.
// If the default value matches a custom format, set that new format
// type as the default format type.
if ($system_short != $short) {
$name = $field_name .'_short';
date_install_create_format($field, NULL, $name, $short);
$map['short'] = $name;
if ($default == $short) {
$field_settings[$new_setting] = $name;
$map['default'] = $name;
}
}
if ($system_med != $med) {
$name = $field_name .'_medium';
date_install_create_format($field, NULL, $name, $med);
$map['medium'] = $name;
if ($default == $med) {
$field_settings[$new_setting] = $name;
$map['default'] = $name;
}
}
if ($system_long != $long) {
$name = $field_name .'_long';
date_install_create_format($field, NULL, $name, $long);
$map['long'] = $name;
if ($default == $long) {
$field_settings[$new_setting] = $name;
$map['default'] = $name;
}
}
// If we haven't found a format type for the default format yet,
// see if it matches any of the system formats.
if (empty($field_settings[$new_setting])) {
if ($default == $system_med) {
$field_settings[$new_setting] = 'medium';
$map['default'] = 'medium';
}
elseif ($default == $system_long) {
$field_settings[$new_setting] = 'long';
$map['default'] = 'long';
}
elseif ($default == $system_short) {
$field_settings[$new_setting] = 'short';
$map['default'] = 'short';
}
else {
// If all else fails, create a new format type
// for the default format.
$name = $field_name .'_default';
date_install_create_format($field, NULL, $name, $default);
$field_settings[$new_setting] = $name;
$map['default'] = $name;
}
}
// Store the map of old and new formats in a variable.
variable_set('date_format_map_'. $field['field_name'], $map);
$ret[] = array('success' => TRUE, 'query' => t('Field %field formats were converted to custom formats.', array('%field' => $field_name)));
foreach ($map as $key => $value) {
if (in_array($key, array('default', 'short', 'medium', 'long'))) {
$ret[] = array('success' => TRUE, 'query' => t("The old format type %from_format for field %field was converted to the new format type %to_format.", array('%field' => $field_name, '%from_format' => $key, '%to_format' => $value)));
}
}
// Unset all the old values, we won't use them any more.
unset($field_settings['output_format_date']);
unset($field_settings['output_format_custom']);
unset($field_settings['output_format_date_short']);
unset($field_settings['output_format_custom_short']);
unset($field_settings['output_format_date_medium']);
unset($field_settings['output_format_custom_medium']);
unset($field_settings['output_format_date_long']);
unset($field_settings['output_format_custom_long']);
db_query("UPDATE {". content_field_tablename() ."} SET global_settings = '%s' WHERE field_name = '%s'", serialize($field_settings), $field_name);
// Update the display settings to point to the new format types.
$result2 = db_query("SELECT * FROM {". content_instance_tablename() ."} WHERE field_name = '$field_name'");
while ($instance = db_fetch_array($result2)) {
$display_settings = unserialize($instance['display_settings']);
foreach ($display_settings as $key => $setting) {
if ($key != 'label' && array_key_exists($display_settings[$key]['format'], $map)) {
$display_settings[$key]['format'] = $map[$display_settings[$key]['format']];
}
}
db_query("UPDATE {". content_instance_tablename() ."} SET display_settings = '%s' WHERE field_name = '%s' AND type_name = '%s'", serialize($display_settings), $field_name, $instance['type_name']);
$ret[] = array('success' => TRUE, 'query' => t("The display settings for field %field in content type %type_name were updated.", array('%field' => $field_name, '%type_name' => $instance['type_name'])));
}
// See if any views stored in the database need date formats updated.
/*
$result2 = db_query("SELECT * FROM {views_display}");
while ($row = db_fetch_array($result2)) {
$updated = FALSE;
$display_options = unserialize($row['display_options']);
if (array_key_exists('fields', $display_options)) {
if (array_key_exists($field_name .'_value', $display_options['fields'])) {
$display_options['fields'][$field_name .'_value']['format'] = $map[$display_options['fields'][$field_name .'_value']['format']];
$updated = TRUE;
}
if (array_key_exists($field_name .'_value2', $display_options['fields'])) {
$display_options['fields'][$field_name .'_value2']['format'] = $map[$display_options['fields'][$field_name .'_value2']['format']];
$updated = TRUE;
}
if ($updated) {
db_query("UPDATE {views_display} SET display_options='%s'", serialize($display_options));
$ret[] = array('success' => TRUE, 'query' => t("The format used for field %field in view %vid %display display was updated to the new format name.", array('%field' => $field_name, '%display' => $row['display_title'], '%vid' => $row['vid'])));
}
}
}
*/
}
// Clear any caches that may have old formats in them.
content_clear_type_cache();
drupal_set_message(t('Date display formats are now included with the system date and time settings. Please visit <a href="@date-time-page">the Date and time format page</a> to see the new format types.', array('@date-time-page' => url('admin/settings/date-time/formats'))));
return $ret;
}
function date_install_create_format($field, $title, $name, $format) {
if (empty($name) || empty($format)) {
return;
}
$fields = content_fields();
if (empty($title)) {
$type = str_replace($field['field_name'] .'_', '', $name);
$field = $fields[$field['field_name']];
$title = $field['widget']['label'];
}
variable_set('date_format_'. $name, $format);
db_query("INSERT INTO {date_format_types} (type, title, locked) VALUES('%s', '%s', 0)", $name, $title);
if (!db_result(db_query("SELECT dfid FROM {date_formats} WHERE format='%s' AND type='%s'", $format, $type))) {
db_query("INSERT INTO {date_formats} (format, type, locked) VALUES('%s', '%s', 0)", $format, 'custom');
}
}
function date_db_integrity($name) {
$ret = array();
if (!module_exists('content') || !module_exists('date_api')) {
return $ret;
}
drupal_load('module', 'content');
require_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
$ret = array();
$fields = content_fields();
foreach ($fields as $field) {
$db_info = content_database_info($field);
if ($field['type'] == 'date' || $field['type'] == 'datestamp') {
$table = $db_info['table'];
// start with the new column patterns.
$columns_start = $db_info['columns'];
$columns_end = $db_info['columns'];
// alter the start column values to invalid or empty
// values to force the new columns to be reset.
$columns_start['value']['length'] = 90;
if (!empty($field['todate'])) {
if (!db_column_exists($table, $columns_start['value2']['column'])) {
unset($columns_start['value2']);
}
else {
$columns_start['value2']['length'] = 80;
}
}
if ($field['tz_handling'] == 'date') {
if (!db_column_exists($table, $columns_start['timezone']['column'])) {
unset($columns_start['timezone']);
}
else {
$columns_start['timezone']['length'] = 80;
}
if (!db_column_exists($table, $columns_start['offset']['column'])) {
unset($columns_start['offset']);
}
else {
$columns_start['offset']['length'] = 80;
}
if ($field['todate']) {
if (!db_column_exists($table, $columns_start['offset2']['column'])) {
unset($columns_start['offset2']);
}
else {
$columns_start['offset2']['length'] = 80;
}
}
}
if (!empty($field['repeat'])) {
if (!db_column_exists($table, $columns_start['rrule']['column'])) {
unset($columns_start['rrule']);
}
else {
$columns_start['rrule']['length'] = 80;
}
}
$start_field = $field;
$start_field['columns'] = $columns_start;
$end_field = $field;
$end_field['columns'] = $columns_end;
content_alter_db($start_field, $end_field);
$message = 'Date database integrity check. Updated table '. $table .' to set all columns to accept NULL values.';
$ret[] = array('success' => TRUE, 'query' => $message);
}
}
content_clear_type_cache();
return $ret;
}

View file

@ -0,0 +1,887 @@
<?php
/**
* @file
* Defines date/time field types for the Content Construction Kit (CCK).
*/
/**
* Implementation of hook_init().
*/
function date_init() {
require_once('./'. drupal_get_path('module', 'date') .'/date.theme');
if (module_exists('token')) {
require_once('./'. drupal_get_path('module', 'date') .'/date_token.inc');
}
}
/**
* Implementation of hook_menu().
*/
function date_menu() {
$items = array();
// Repeat dates tab on node
if (!module_exists('date_repeat')) {
return $items;
}
$items['node/%node/repeats'] = array(
'title' => 'Repeats',
'page callback' => 'date_repeat_page',
'page arguments' => array(1),
'access callback' => 'date_repeat_node',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
function date_perm() {
return array('view date repeats');
}
function date_repeat_node($node) {
if (date_repeat_type($node->type)) {
return user_access('view date repeats');
}
return FALSE;
}
function date_repeat_type($type_name) {
$type = content_types($type_name);
if (!empty($type['fields'])) {
foreach ($type['fields'] as $field_name => $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && $field['repeat']) {
return TRUE;
}
}
}
return FALSE;
}
function date_repeat_fields($type_name) {
$type = content_types($type_name);
$fields = array();
if (!empty($type['fields'])) {
foreach ($type['fields'] as $field_name => $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && $field['repeat']) {
$fields[] = $field_name;
}
}
}
return $fields;
}
function date_repeat_page($node) {
drupal_set_title(check_plain($node->title));
$node->date_repeat_show_all = TRUE;
$node->build_mode = NODE_BUILD_NORMAL;
$node->content = array();
$field_names = date_repeat_fields($node->type);
$view = content_view($node, FALSE, TRUE);
$output = '';
foreach ($field_names as $field_name) {
$output .= drupal_render($node->content[$field_name]);
}
return $output;
}
function date_is_repeat_field($field) {
$repeat_widgets = array(
'date_select_repeat',
'date_text_repeat',
'date_popup_repeat',
);
if (in_array($field['widget']['type'], $repeat_widgets)) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook_content_is_empty().
*/
function date_content_is_empty($item, $field) {
if (empty($item['value'])) {
return TRUE;
}
elseif ($field['todate'] == 'required' && empty($item['value2'])) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook_field_info().
*/
function date_field_info() {
return array(
'date' => array(
'label' => 'Date',
'description' => t('Store a date in the database as an ISO date, recommended for historical or partial dates.'),
),
'datestamp' => array(
'label' => 'Datestamp',
'description' => t('Store a date in the database as a timestamp, deprecated format to suppport legacy data.'),
),
'datetime' => array(
'label' => 'Datetime',
'description' => t('Store a date in the database as a datetime field, recommended for complete dates and times that may need timezone conversion.'),
),
);
}
/**
* Implementation of hook_widget_info().
*/
function date_widget_info() {
$info = array(
'date_select' => array(
'label' => t('Select List'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
),
'date_select_repeat' => array(
'label' => t('Select List with Repeat options'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_MODULE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
),
'date_text' => array(
'label' => t('Text Field with custom input format'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
),
'date_text_repeat' => array(
'label' => t('Text Field with Repeat options'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_MODULE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
),
);
if (module_exists('date_popup')) {
$info['date_popup'] = array(
'label' => t('Text Field with Date Pop-up calendar'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
);
$info['date_popup_repeat'] = array(
'label' => t('Text Field with Date Pop-up and Repeat options'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_MODULE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_CUSTOM,
),
);
}
if (!module_exists('date_repeat')) {
unset($info['date_select_repeat']);
unset($info['date_text_repeat']);
if (isset($info['date_popup_repeat'])) {
unset($info['date_popup_repeat']);
}
}
return $info;
}
function date_default_format($type) {
if (stristr($type, 'date_popup') && module_exists('date_popup')) {
$formats = date_popup_formats();
$default_format = array_shift($formats);
}
else {
// example input formats must show all possible date parts, so add seconds.
$default_format = str_replace('i', 'i:s', variable_get('date_format_short', 'm/d/Y - H:i'));
}
return $default_format;
}
function date_input_value($field, $element) {
switch ($field['widget']['type']) {
case 'date_text':
case 'date_text_repeat':
$function = 'date_text_input_value';
break;
case 'date_popup':
case 'date_popup_repeat':
$function = 'date_popup_input_value';
break;
default:
$function = 'date_select_input_value';
}
return $function($element);
}
/**
* Implementation of hook_field_formatter_info().
*/
function date_field_formatter_info() {
$formatters = array(
'default' => array('label' => t('Default'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE),
'format_interval' => array('label' => t('As Time Ago'),
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE),
);
$format_types = date_get_format_types('', TRUE);
if (!empty($format_types)) {
foreach ($format_types as $type => $type_info) {
$formatters[$type] = array(
'label' => $type_info['title'],
'field types' => array('date', 'datestamp', 'datetime'),
'multiple values' => CONTENT_HANDLE_CORE,
);
}
}
return $formatters;
}
/**
* Implementation of hook_theme().
*/
function date_theme() {
$path = drupal_get_path('module', 'date');
require_once "./$path/date.theme";
$base = array(
'file' => 'date.theme',
'path' => "$path",
);
$themes = array(
'date_combo' => array(
'arguments' => array('element' => NULL)),
'date_all_day' => array(
'arguments' => array(
'which' => NULL, 'date1' => NULL, 'date2' => NULL,
'format' => NULL, 'node' => NULL, 'view' => NULL)),
'date_all_day_label' => array(
'arguments' => array()),
'date_display_single' => array(
'arguments' => array('date' => NULL, 'timezone' => NULL)),
'date_display_range' => array(
'arguments' => array('date1' => NULL, 'date2' => NULL, 'timezone' => NULL)),
'date_text_parts' => array(
'arguments' => array('element' => NULL)),
'date' => array(
'arguments' => array('element' => NULL)),
'date_formatter_default' => $base + array(
'arguments' => array('element' => NULL),
'function' => 'theme_date_display_combination'),
'date_formatter_format_interval' => $base + array(
'arguments' => array('element' => NULL),
'function' => 'theme_date_format_interval'),
'date_formatter_format_calendar_day' => $base + array(
'arguments' => array('element' => NULL),
'function' => 'theme_date_format_calendar_day'),
'date_repeat_display' => $base + array(
'arguments' => array('field' => NULL,
'item' => NULL, 'node' => NULL, 'dates' => NULL),
'function' => 'theme_date_repeat_display',
),
);
// Table isn't available first time date_theme() is called in update.php.
if (db_table_exists('date_format_types')) {
$format_types = date_get_format_types('', TRUE);
if (!empty($format_types)) {
foreach ($format_types as $type => $type_info) {
$themes['date_formatter_' . $type] = $base + array(
'arguments' => array('element' => NULL),
'function' => 'theme_date_display_combination',
);
}
}
}
return $themes;
}
/**
* Helper function for creating formatted date arrays from a formatter.
*
* Use the Date API to get an object representation of a date field
*
* @param array $field
* @param array $item - a node field item, like $node->myfield[0]
*
* @return array that holds the From and To date objects
* Each date object looks like:
* date [value] => array(
* [db] => array( // the value stored in the database
* [object] => the datetime object
* [datetime] => 2007-02-15 20:00:00
* )
* [local] => array( // the local representation of that value
* [object] => the datetime object
* [datetime] => 2007-02-15 14:00:00
* [timezone] => US/Central
* [offset] => -21600
* )
* )
*/
function date_formatter_process($element) {
$node = $element['#node'];
$dates = array();
$timezone = date_default_timezone_name();
if (empty($timezone)) {
return $dates;
}
$field_name = $element['#field_name'];
$fields = content_fields();
$field = $fields[$field_name];
$formatter = $element['#formatter'];
$format = date_formatter_format($formatter, $field_name);
$item = $element['#item'];
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
$timezone = date_get_timezone($field['tz_handling'], $timezone);
$timezone_db = date_get_timezone_db($field['tz_handling']);
$process = date_process_values($field);
foreach ($process as $processed) {
if (empty($item[$processed])) {
$dates[$processed] = NULL;
}
else {
// create a date object with a gmt timezone from the database value
$value = $item[$processed];
if ($field['type'] == DATE_ISO) {
$value = str_replace(' ', 'T', date_fuzzy_datetime($value));
}
$date = date_make_date($value, $timezone_db, $field['type'], $field['granularity']);
$dates[$processed] = array();
$dates[$processed]['db']['object'] = $date;
$dates[$processed]['db']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
date_timezone_set($date, timezone_open($timezone));
$dates[$processed]['local']['object'] = $date;
$dates[$processed]['local']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
$dates[$processed]['local']['timezone'] = $timezone;
$dates[$processed]['local']['offset'] = date_offset_get($date);
//format the date, special casing the 'interval' format which doesn't need to be processed
$dates[$processed]['formatted'] = '';
if (is_object($date)) {
if ($format == 'format_interval') {
$dates[$processed]['interval'] = date_format_interval($date);
}
elseif ($format == 'format_calendar_day') {
$dates[$processed]['calendar_day'] = date_format_calendar_day($date);
}
elseif ($format == 'U') {
$dates[$processed]['formatted'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_date'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_time'] = '';
$dates[$processed]['formatted_timezone'] = '';
}
elseif (!empty($format)) {
$dates[$processed]['formatted'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_date'] = date_format_date($date, 'custom', date_limit_format($format, array('year', 'month', 'day')));
$dates[$processed]['formatted_time'] = date_format_date($date, 'custom', date_limit_format($format, array('hour', 'minute', 'second')));
$dates[$processed]['formatted_timezone'] = date_format_date($date, 'custom', date_limit_format($format, array('timezone')));
}
}
}
}
if (empty($dates['value2'])) {
$dates['value2'] = $dates['value'];
}
$date1 = $dates['value']['local']['object'];
$date2 = $dates['value2']['local']['object'];
$all_day = '';
$all_day2 = '';
if ($format != 'format_interval') {
$all_day1 = theme('date_all_day', $field, 'date1', $date1, $date2, $format, $node);
$all_day2 = theme('date_all_day', $field, 'date2', $date1, $date2, $format, $node);
}
if ((!empty($all_day1) && $all_day1 != $dates['value']['formatted'])
|| (!empty($all_day2) && $all_day2 != $dates['value2']['formatted'])) {
$dates['value']['formatted_time'] = theme('date_all_day_label');
$dates['value2']['formatted_time'] = theme('date_all_day_label');
$dates['value']['formatted'] = $all_day1;
$dates['value2']['formatted'] = $all_day2;
}
$dates['format'] = $format;
return $dates;
}
/**
* $field['granularity'] will contain an array like ('hour' => 'hour', 'month' => 0)
* where the values turned on return their own names and the values turned off return a zero
* need to reconfigure this into a simple array of the turned on values
*/
function date_granularity($field) {
if (!is_array($field) || !is_array($field['granularity'])) {
$field['granularity'] = drupal_map_assoc(array('year', 'month', 'day'));
}
return array_values(array_filter($field['granularity']));
}
/**
* Helper function to create an array of the date values in a
* field that need to be processed.
*/
function date_process_values($field) {
return $field['todate'] ? array('value', 'value2') : array('value');
}
/**
* Implementation of hook_help().
*/
function date_help($path, $arg) {
switch ($path) {
case 'admin/help#date':
return '<p>' . t('Complete documentation for the Date and Date API modules is available at <a href="@link">http://drupal.org/node/92460</a>.', array('@link' => 'http://drupal.org/node/92460')) . '</p>';
break;
}
}
/**
* Implementation of hook_form_alter().
* Make sure date information gets updated.
*/
function date_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'content_display_overview_form') {
date_content_display_form($form, $form_state);
}
}
/**
* Implementation of hook_field().
*
* Validation and submission operation code is moved into a separate
* file and included only when processing forms.
*/
function date_field($op, &$node, $field, &$items, $teaser, $page) {
// Add some information needed to interpret token values.
$additions[$field['field_name']] = $items;
foreach ($items as $delta => $item) {
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
if (is_array($additions[$field['field_name']][$delta])) {
$additions[$field['field_name']][$delta]['timezone'] = date_get_timezone($field['tz_handling'], $timezone);
$additions[$field['field_name']][$delta]['timezone_db'] = date_get_timezone_db($field['tz_handling']);
$additions[$field['field_name']][$delta]['date_type'] = $field['type'];
}
}
switch ($op) {
case 'load':
return $additions;
break;
case 'validate':
require_once('./'. drupal_get_path('module', 'date') .'/date_elements.inc');
return _date_field_validate($op, $node, $field, $items, $teaser, $page);
break;
case 'presave':
case 'insert':
case 'update':
require_once('./'. drupal_get_path('module', 'date') .'/date_elements.inc');
$items = $additions[$field['field_name']];
if ($additions[$field['field_name']]) {
$node->$field['field_name'] = $additions;
}
return _date_field_update($op, $node, $field, $items, $teaser, $page);
break;
case 'sanitize':
//foreach ($items as $delta => $item) {
//$dates = date_formatter_process($field, $item, $node, $formatter);
//$node->$field['field_name'][$delta]['dates'] = $dates;
//}
}
}
/**
* Implementation of hook_widget().
*
* This code and all the processes it uses are in a separate file,
* included only when processing forms.
*/
function date_widget(&$form, &$form_state, &$field, $items, $delta) {
require_once('./'. drupal_get_path('module', 'date') .'/date_elements.inc');
return _date_widget($form, $form_state, $field, $items, $delta);
}
/**
* Implementation of hook_elements().
*
* This code and all the processes it uses are in a separate file,
* included only when processing forms.
*/
function date_elements() {
require_once('./'. drupal_get_path('module', 'date') .'/date_elements.inc');
return _date_elements();
}
/**
* Implementation of Devel module's hook_content_generate().
*/
function date_content_generate($node, $field) {
require_once('./'. drupal_get_path('module', 'date') .'/date_content_generate.inc');
return _date_content_generate($node, $field);
}
/**
* Wrapper functions for date administration, included only when
* processing field settings.
*/
function date_widget_settings($op, $widget) {
require_once('./'. drupal_get_path('module', 'date') .'/date_admin.inc');
return _date_widget_settings($op, $widget);
}
function date_field_settings($op, $field) {
require_once('./'. drupal_get_path('module', 'date') .'/date_admin.inc');
return _date_field_settings($op, $field);
}
function date_formatter_settings($form_state = NULL, $field, $options = array(), $views_form = FALSE) {
require_once('./'. drupal_get_path('module', 'date') .'/date_admin.inc');
return _date_formatter_settings($form_state, $field, $options, $views_form);
}
/**
* Helper function to return the date format used by a specific formatter.
*/
function date_formatter_format($formatter, $field_name) {
$fields = content_fields();
$field = $fields[$field_name];
$default = variable_get('date_format_medium', 'D, m/d/Y - H:i');
switch ($formatter) {
case 'format_interval':
return 'format_interval';
case 'default':
$format = variable_get('date_format_'. $field['default_format'], $default);
break;
default:
$format = variable_get('date_format_'. $formatter, $default);
break;
}
if (empty($format)) {
$format = $default;
}
// A selected format might include timezone information.
$granularity = date_granularity($field);
array_push($granularity, 'timezone');
return date_limit_format($format, $granularity);
}
/**
* Implementation of hook_views_api().
*/
function date_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'date'),
);
}
/**
* Helper function to adapt node date fields to formatter settings.
*/
function date_prepare_node($node, $field, $type_name, $context, $options) {
// If there are options to limit multiple values,
// alter the node values to match.
$field_name = $field['field_name'];
$max_count = $options['multiple']['multiple_number'];
// If no results should be shown, empty the values and return.
if (is_numeric($max_count) && $max_count == 0) {
$node->{$field_name} = array();
return $node;
}
// Otherwise removed values that should not be displayed.
if (!empty($options['multiple']['multiple_from']) || !empty($options['multiple']['multiple_to']) || !empty($max_count)) {
$format = date_type_format($field['type']);
include_once(drupal_get_path('module', 'date_api') .'/date_api_sql.inc');
$date_handler = new date_sql_handler($field);
$arg0 = !empty($options['multiple']['multiple_from']) ? date_format(date_create($options['multiple']['multiple_from'], date_default_timezone()), DATE_FORMAT_DATETIME) : variable_get('date_min_year', 100) .'-01-01T00:00:00';
$arg1 = !empty($options['multiple']['multiple_to']) ? date_format(date_create($options['multiple']['multiple_to'], date_default_timezone()), DATE_FORMAT_DATETIME) : variable_get('date_max_year', 4000) .'-12-31T23:59:59';
if (!empty($arg0) && !empty($arg1)) {
$arg = $arg0 .'--'. $arg1;
}
elseif (!empty($arg0)) {
$arg = $arg0;
}
elseif (!empty($arg1)) {
$arg = $arg1;
}
if (!empty($arg)) {
$range = $date_handler->arg_range($arg);
$start = date_format($range[0], $format);
$end = date_format($range[1], $format);
// Empty out values we don't want to see.
$count = 0;
foreach ($node->$field_name as $delta => $value) {
if (!empty($node->date_repeat_show_all)) {
break;
}
elseif ((!empty($max_count) && is_numeric($max_count) && $count >= $max_count) ||
(!empty($value['value']) && $value['value'] < $start) ||
(!empty($value['value2']) && $value['value2'] > $end)) {
unset($node->{$field_name}[$delta]);
}
else {
$count++;
}
}
}
}
return $node;
}
/**
* Identify all fields in this view that use the CCK Date handler.
*/
function date_handler_fields($view) {
$field_names = array();
if (empty($view->date_info->date_fields)) {
if (empty($view->date_info)) {
$view->date_info = new stdClass;
}
$view->date_info->date_fields = array();
}
foreach ($view->field as $field) {
if ($field->definition['handler'] == 'date_handler_field_multiple') {
$name = $field->field;
$group = $field->options['multiple'];
if (drupal_substr($name, -7) == '_value2') {
$field_name = drupal_substr($name, 0, strlen($name) - 7);
}
elseif (drupal_substr($name, -6) == '_value') {
$field_name = drupal_substr($name, 0, strlen($name) - 6);
}
else {
$field_name = '';
$group = array();
continue;
}
foreach ($view->date_info->date_fields as $date_field) {
if (strstr($date_field, '.'. $field_name)) {
$delta_field = 'node_data_'. $field_name .'_delta';
$field_names[$field_name] = array('options' => $group, 'delta_field' => $delta_field, 'view_field' => drupal_clone($field));
// Get rid of the huge view object in the field handler.
unset($field_names[$field_name]['view_field']->view);
}
}
}
}
return $field_names;
}
/**
* Generate a DateAPI SQL handler for the given CCK date field.
*
* The handler will be set up to make the correct timezone adjustments
* for the field settings.
*
* @param $field
* - a $field array.
* @param $compare_tz
* - the timezone used for comparison values in the SQL.
*/
function date_field_get_sql_handler($field, $compare_tz = NULL) {
module_load_include('inc', 'date_api', 'date_api_sql');
$db_info = content_database_info($field);
// Create a DateAPI SQL handler class for this field type.
$handler = new date_sql_handler();
$handler->construct($field['type']);
// If this date field stores a timezone in the DB, tell the handler about it.
if ($field['tz_handling'] == 'date') {
$handler->db_timezone_field = $db_info['columns']['timezone']['column'];
}
else {
$handler->db_timezone = date_get_timezone_db($field['tz_handling']);
}
if (empty($compare_tz)) {
$compare_tz = date_get_timezone($field['tz_handling']);
}
$handler->local_timezone = $compare_tz;
// Now that the handler is properly initialized, force the DB
// to use UTC so no timezone conversions get added to things like
// NOW() or FROM_UNIXTIME().
$handler->set_db_timezone();
return $handler;
}
/**
* Implementation of hook_form_alter().
*
* Adding ability to configure new date format types.
*/
function date_form_date_api_date_formats_form_alter(&$form, $form_state, $form_id = 'date_api_date_formats_form') {
// Add form entry field for adding new format type.
$form['add_format_type'] = array(
'#type' => 'fieldset',
'#title' => t('Add format type'),
'#weight' => 1,
);
$form['add_format_type']['add_date_format_title'] = array(
'#title' => t('Name'),
'#description' => t('The human-readable name for this format type.'),
'#type' => 'textfield',
'#size' => 20,
'#prefix' => '<div class="date-container"><div class="date-format-name">',
'#suffix' => '</div>',
);
$form['add_format_type']['add_date_format_type'] = array(
'#title' => t('Type'),
'#description' => t('The machine-readable name of this format type. <br>This name must contain only lowercase letters, numbers, and underscores and must be unique.'),
'#type' => 'textfield',
'#size' => 20,
'#prefix' => '<div class="date-format-type">',
'#suffix' => '</div></div>',
);
$form['#submit'][] = 'date_date_time_settings_submit';
$form['#validate'][] = 'date_date_time_settings_validate';
$form['buttons']['#weight'] = 5;
}
/**
* Validate new date format type details.
*/
function date_date_time_settings_validate($form, &$form_state) {
if (!empty($form_state['values']['add_date_format_type']) && !empty($form_state['values']['add_date_format_title'])) {
if (!preg_match("/^[a-zA-Z0-9_]+$/", $form_state['values']['add_date_format_type'])) {
form_set_error('add_date_format_type', t('The format type must contain only alphanumeric characters and underscores.'));
}
$types = date_get_format_types();
if (in_array($form_state['values']['add_date_format_type'], array_keys($types))) {
form_set_error('add_date_format_type', t('This format type already exists. Please enter a unique type.'));
}
}
}
/**
* Save date format type to database.
*/
function date_date_time_settings_submit($form, &$form_state) {
if (!empty($form_state['values']['add_date_format_type']) && !empty($form_state['values']['add_date_format_title'])) {
$format_type = array();
$format_type['title'] = $form_state['values']['add_date_format_title'];
$format_type['type'] = $form_state['values']['add_date_format_type'];
$format_type['locked'] = 0;
$format_type['is_new'] = 1;
date_format_type_save($format_type);
}
// Unset, to prevent this getting saved as a variables.
unset($form_state['values']['add_date_format_type']);
unset($form_state['values']['add_date_format_title']);
}
/**
* Insert Date field formatter settings into the Display Fields form.
*/
function date_content_display_form(&$form, &$form_state) {
$fields = content_fields();
$date_fields = array();
foreach ($fields as $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime'))) {
$date_fields[$field['field_name']] = $field;
}
}
foreach ($form as $field_name => $element) {
if (drupal_substr($field_name, 0, 6) == 'field_') {
if (array_key_exists($field_name, $date_fields)) {
$field = $date_fields[$field_name];
foreach ($element as $context => $value) {
if (!in_array($context, array('human_name', 'weight', 'parent', 'label'))) {
$options['type_name'] = $form['#type_name'];
$options['context'] = $context;
$base_form = $form[$field_name][$context]['format'];
$form[$field_name][$context]['format'] = array();
$form[$field_name][$context]['format']['base'] = $base_form;
$form[$field_name][$context]['format']['extra'] = date_formatter_settings($form_state, $field, $options);
$form[$field_name][$context]['format']['#element_validate'] = array('date_formatter_settings_validate');
}
}
}
}
}
}
/**
* Store the formatter settings
* and reset the form back to the value CCK expects.
*/
function date_formatter_settings_validate(&$form, &$form_state) {
$field = $form['extra']['field']['#value'];
$field_name = $field['field_name'];
$type_name = $form['extra']['type_name']['#value'];
$context = $form['extra']['context']['#value'];
$form_values = $form_state['values'][$field_name][$context]['format']['extra'];
$value = 'date:'. $type_name .':'. $context .':'. $field_name;
variable_set($value .'_show_repeat_rule', $form_values['repeat']['show_repeat_rule']);
variable_set($value .'_multiple_number', $form_values['multiple']['multiple_number']);
variable_set($value .'_multiple_from', $form_values['multiple']['multiple_from']);
variable_set($value .'_multiple_to', $form_values['multiple']['multiple_to']);
variable_set($value .'_fromto', $form_values['fromto']['fromto']);
form_set_value($form, $form_state['values'][$field_name][$context]['format']['base'], $form_state);
}
function date_formatter_get_settings($field_name, $type_name, $context) {
$options = array();
$value = 'date:'. $type_name .':'. $context .':'. $field_name;
$options['repeat']['show_repeat_rule'] = variable_get($value .'_show_repeat_rule', 'show');
$options['multiple']['multiple_number'] = variable_get($value .'_multiple_number', '');
$options['multiple']['multiple_from'] = variable_get($value .'_multiple_from', '');
$options['multiple']['multiple_to'] = variable_get($value .'_multiple_to', '');
$options['fromto']['fromto'] = variable_get($value .'_fromto', 'both');
return $options;
}
/**
* Determine if a from/to date combination qualify as 'All day'.
*
* @param array $field, the field definition for this date field.
* @param object $date1, a date/time object for the 'from' date.
* @param object $date2, a date/time object for the 'to' date.
* @return TRUE or FALSE.
*/
function date_field_all_day($field, $date1, $date2 = NULL) {
if (empty($date1) || !is_object($date1)) {
return FALSE;
}
elseif (!date_has_time($field['granularity'])) {
return TRUE;
}
if (empty($date2)) {
$date2 = $date1;
}
$granularity = date_granularity_precision($field['granularity']);
$increment = isset($field['widget']['increment']) ? $field['widget']['increment'] : 1;
$date1 = date_format($date1, DATE_FORMAT_DATETIME);
$date2 = date_format($date2, DATE_FORMAT_DATETIME);
return date_is_all_day($date1, $date2, $granularity, $increment);
}

View file

@ -0,0 +1,283 @@
<?php
/**
* @file
* Theme functions.
*/
/**
* @addtogroup themeable
* @{
*
* Formatter themes
*/
/**
* Theme from/to date combination in the view.
*
* Useful values:
*
* $node->date_id
* If set, this will show only an individual date on a field with
* multiple dates. The value should be a string that contains
* the following values, separated with periods:
* - module name of the module adding the item
* - node nid
* - field name
* - delta value of the field to be displayed
* - other information the module's custom theme might need
*
* Used by the calendar module and available for other uses.
* example: 'date.217.field_date.3.test'
*
* $node->date_repeat_show
* If true, tells the theme to show all the computed values
* of a repeating date. If not true or not set, only the
* start date and the repeat rule will be displayed.
*
* $dates['format'] - the format string used on these dates
* $dates['value']['local']['object'] - the local date object for the From date
* $dates['value2']['local']['object'] - the local date object for the To date
* $dates['value']['local']['datetime'] - the datetime value of the From date database (GMT) value
* $dates['value2']['local']['datetime'] - the datetime value of the To date database (GMT) value
* $dates['value']['formatted'] = formatted From date, i.e. 'February 15, 2007 2:00 pm';
* $dates['value']['formatted_date'] - only the date part of the formatted From date
* $dates['value']['formatted_time'] - only the time part of the formatted From date
* $dates['value2']['formatted'] = formatted To date, i.e. 'February 15, 2007 6:00 pm';
* $dates['value2']['formatted_date'] - only the date part of the formatted To date
* $dates['value2']['formatted_time'] - only the time part of the formatted To date
*/
function theme_date_display_combination($element) {
static $repeating_ids = array();
$node = $element['#node'];
$field_name = $element['#field_name'];
$context = !empty($node->content) && !empty($node->content[$field_name]) ? $node->content[$field_name]['#context'] : 'full';
$type_name = $element['#type_name'];
$fields = content_fields();
$field = $fields[$field_name];
$item = $element['#item'];
// Get the formatter settings, either the default settings for this node
// type or the View settings stored in $node->date_info.
$options = date_formatter_get_settings($field_name, $type_name, $context);
if (!empty($node->date_info) && !empty($node->date_info->formatter_settings)) {
$options = $node->date_info->formatter_settings;
}
$output = '';
// If date_id is set for this field and the delta doesn't match, don't display it.
if (!empty($node->date_id)) {
foreach ((array) $node->date_id as $key => $id) {
list($module, $nid, $field_name, $delta, $other) = explode('.', $id);
if ($field_name == $field['field_name'] && isset($item['#delta']) && $delta != $item['#delta']) {
return $output;
}
}
}
// Check the formatter settings to see if the repeat rule should be
// displayed. Show it only with the first multiple value date.
if (!in_array($node->nid, $repeating_ids) && module_exists('date_repeat')
&& !empty($item['rrule']) && $options['repeat']['show_repeat_rule'] == 'show') {
require_once('./'. drupal_get_path('module', 'date') .'/date_repeat.inc');
$output .= theme('date_repeat_display', $field, $item, $node);
$repeating_ids[] = $node->nid;
}
// If this is a full node or a pseudo node created by grouping
// multiple values, see exactly which values are supposed to be visible.
if (isset($node->$field_name)) {
$node = date_prepare_node($node, $field, $type_name, $context, $options);
// Did the current value get removed by formatter settings?
if (empty($node->{$field_name}[$item['#delta']])) {
return $output;
}
// Adjust the $element values to match the changes.
$element['#node'] = $node;
}
// Call the right theme for this formatter.
// Update the element with values that might have been altered by
// date_prepare_node() and figure out which values to display.
$dates = date_formatter_process($element);
switch ($options['fromto']['fromto']) {
case 'value':
$date1 = $dates['value']['formatted'];
$date2 = $date1;
break;
case 'value2':
$date2 = $dates['value2']['formatted'];
$date1 = $date2;
break;
default:
$date1 = $dates['value']['formatted'];
$date2 = $dates['value2']['formatted'];
break;
}
// Pull the timezone, if any, out of the formatted result and tack it
// back on at the end, if it is in the current formatted date.
$timezone = $dates['value']['formatted_timezone'];
if ($timezone) {
$timezone = ' ' . $timezone;
}
$date1 = str_replace($timezone, '', $date1);
$date2 = str_replace($timezone, '', $date2);
// No date values, display nothing.
if (empty($date1) && empty($date2)) {
$output .= '';
}
// From and To dates match or there is no To date, display a complete single date.
elseif ($date1 == $date2 || empty($date2)) {
$output .= theme('date_display_single', $date1, $timezone);
}
// Same day, different times, don't repeat the date but show both From and To times.
elseif (date_has_time($field['granularity']) && $dates['value']['formatted_date'] == $dates['value2']['formatted_date']) {
// Replace the original time with the from/to time in the formatted start date.
// Make sure that parentheses or brackets wrapping the time will be retained in the
// final result.
$time1 = preg_replace('`^([\(\[])`', '', $dates['value']['formatted_time']);
$time1 = preg_replace('([\)\]]$)', '', $time1);
$time2 = preg_replace('`^([\(\[])`', '', $dates['value2']['formatted_time']);
$time2 = preg_replace('([\)\]]$)', '', $time2);
$time = theme('date_display_range', $time1, $time2);
$replaced = str_replace($time1, $time, $date1);
$output .= theme('date_display_single', $replaced, $timezone);
}
// Different days, display both in their entirety.
else {
$output .= theme('date_display_range', $date1, $date2, $timezone);
}
return $output;
}
function theme_date_display_single($date, $timezone = NULL) {
return '<span class="date-display-single">'. $date . $timezone .'</span>';
}
function theme_date_display_range($date1, $date2, $timezone = NULL) {
return '<span class="date-display-start">'. $date1 .'</span>'.
'<span class="date-display-separator"> - </span>' .
'<span class="date-display-end">'. $date2 . $timezone. '</span>';
}
/**
* Theme a format interval for a date element
*
* @param $field = the field settings
* @param $node = node information, this is not always available and not
* always the full node, it depends on what value was provided to the formatter.
* Only the nid is always guaranteed to be available.
* @param $dates - an array of date information, see explanation for date_field_object for details.
* @return a formatted display
*
*/
function theme_date_format_interval($element) {
$node = $element['#node'];
$field_name = $element['#field_name'];
$context = !empty($node->content) ? $node->content[$field_name]['#context'] : 'full';
$type_name = $element['#type_name'];
$fields = content_fields();
$field = $fields[$field_name];
$item = $element['#item'];
// Get the formatter settings, either the default settings for this node
// type or the View settings stored in $node->date_info.
$options = date_formatter_get_settings($field_name, $type_name, $context);
if (!empty($node->date_info) && !empty($node->date_info->formatter_settings)) {
$options = $node->date_info->formatter_settings;
}
// If date_id is set for this field and the delta doesn't match, don't display it.
if (!empty($node->date_id)) {
foreach ((array) $node->date_id as $key => $id) {
list($module, $nid, $field_name, $delta, $other) = explode('.', $id);
if ($field_name == $field['field_name'] && isset($item['#delta']) && $delta != $item['#delta']) {
return;
}
}
}
// If this is not coming from Views, it is the full node.
// If we aren't retrieving a specific value, adjust the node values
// to match the formatter settings, removing values we should not see.
if (!empty($node->content) && empty($node->date_id)) {
$node = date_prepare_node($node, $field, $type_name, $context, $options);
// Did the current value get removed by formatter settings?
if (empty($node->{$field_name}[$item['#delta']])) {
return;
}
// Adjust the $element values to match the changes.
$element['#node'] = $node;
}
$dates = date_formatter_process($element);
return theme('date_time_ago', $dates['value']['local']['object'], $dates['value2']['local']['object']);
}
/**
* Theme the human-readable description for a Date Repeat rule.
*
* TODO -
* add in ways to store the description in the date so it isn't regenerated
* over and over and find a way to allow description to be shown or hidden.
*/
function theme_date_repeat_display($field, $item, $node = NULL) {
$output = '';
if (!empty($item['rrule'])) {
$output = date_repeat_rrule_description($item['rrule']);
$output = '<div>'. $output .'</div>';
}
return $output;
}
/**
* Adjust from/to date format to account for 'all day'.
*
* @param array $field, the field definition for this date field.
* @param string $which, which value to return, 'date1' or 'date2'.
* @param object $date1, a date/time object for the 'from' date.
* @param object $date2, a date/time object for the 'to' date.
* @param string $format
* @param object $node, the node this date comes from (may be incomplete, always contains nid).
* @param object $view, the view this node comes from, if applicable.
* @return formatted date.
*/
function theme_date_all_day($field, $which, $date1, $date2, $format, $node, $view = NULL) {
if (empty($date1) || !is_object($date1) || $format == 'format_interval') {
return;
}
if (empty($date2)) {
$date2 = $date1;
}
$suffix = '';
if (!date_has_time($field['granularity'])) {
$format = date_limit_format($format, array('year', 'month', 'day'));
}
else {
$format_granularity = date_format_order($format);
$format_has_time = FALSE;
if (in_array('hour', $format_granularity)) {
$format_has_time = TRUE;
}
$all_day = date_field_all_day($field, $date1, $date2);
if ($all_day && $format_has_time) {
$format = date_limit_format($format, array('year', 'month', 'day'));
# $suffix = ' ' . theme('date_all_day_label');
}
}
return trim(date_format_date($$which, 'custom', $format) . $suffix);
}
/**
* Theme the way an 'all day' label will look.
*/
function theme_date_all_day_label() {
# return '('. date_t('All day', 'datetime') .')';
return '';
}
/** @} End of addtogroup themeable */

View file

@ -0,0 +1,282 @@
<?php
/**
* @file
* Theme functions.
*/
/**
* @addtogroup themeable
* @{
*
* Formatter themes
*/
/**
* Theme from/to date combination in the view.
*
* Useful values:
*
* $node->date_id
* If set, this will show only an individual date on a field with
* multiple dates. The value should be a string that contains
* the following values, separated with periods:
* - module name of the module adding the item
* - node nid
* - field name
* - delta value of the field to be displayed
* - other information the module's custom theme might need
*
* Used by the calendar module and available for other uses.
* example: 'date.217.field_date.3.test'
*
* $node->date_repeat_show
* If true, tells the theme to show all the computed values
* of a repeating date. If not true or not set, only the
* start date and the repeat rule will be displayed.
*
* $dates['format'] - the format string used on these dates
* $dates['value']['local']['object'] - the local date object for the From date
* $dates['value2']['local']['object'] - the local date object for the To date
* $dates['value']['local']['datetime'] - the datetime value of the From date database (GMT) value
* $dates['value2']['local']['datetime'] - the datetime value of the To date database (GMT) value
* $dates['value']['formatted'] = formatted From date, i.e. 'February 15, 2007 2:00 pm';
* $dates['value']['formatted_date'] - only the date part of the formatted From date
* $dates['value']['formatted_time'] - only the time part of the formatted From date
* $dates['value2']['formatted'] = formatted To date, i.e. 'February 15, 2007 6:00 pm';
* $dates['value2']['formatted_date'] - only the date part of the formatted To date
* $dates['value2']['formatted_time'] - only the time part of the formatted To date
*/
function theme_date_display_combination($element) {
static $repeating_ids = array();
$node = $element['#node'];
$field_name = $element['#field_name'];
$context = !empty($node->content) && !empty($node->content[$field_name]) ? $node->content[$field_name]['#context'] : 'full';
$type_name = $element['#type_name'];
$fields = content_fields();
$field = $fields[$field_name];
$item = $element['#item'];
// Get the formatter settings, either the default settings for this node
// type or the View settings stored in $node->date_info.
$options = date_formatter_get_settings($field_name, $type_name, $context);
if (!empty($node->date_info) && !empty($node->date_info->formatter_settings)) {
$options = $node->date_info->formatter_settings;
}
$output = '';
// If date_id is set for this field and the delta doesn't match, don't display it.
if (!empty($node->date_id)) {
foreach ((array) $node->date_id as $key => $id) {
list($module, $nid, $field_name, $delta, $other) = explode('.', $id);
if ($field_name == $field['field_name'] && isset($item['#delta']) && $delta != $item['#delta']) {
return $output;
}
}
}
// Check the formatter settings to see if the repeat rule should be
// displayed. Show it only with the first multiple value date.
if (!in_array($node->nid, $repeating_ids) && module_exists('date_repeat')
&& !empty($item['rrule']) && $options['repeat']['show_repeat_rule'] == 'show') {
require_once('./'. drupal_get_path('module', 'date') .'/date_repeat.inc');
$output .= theme('date_repeat_display', $field, $item, $node);
$repeating_ids[] = $node->nid;
}
// If this is a full node or a pseudo node created by grouping
// multiple values, see exactly which values are supposed to be visible.
if (isset($node->$field_name)) {
$node = date_prepare_node($node, $field, $type_name, $context, $options);
// Did the current value get removed by formatter settings?
if (empty($node->{$field_name}[$item['#delta']])) {
return $output;
}
// Adjust the $element values to match the changes.
$element['#node'] = $node;
}
// Call the right theme for this formatter.
// Update the element with values that might have been altered by
// date_prepare_node() and figure out which values to display.
$dates = date_formatter_process($element);
switch ($options['fromto']['fromto']) {
case 'value':
$date1 = $dates['value']['formatted'];
$date2 = $date1;
break;
case 'value2':
$date2 = $dates['value2']['formatted'];
$date1 = $date2;
break;
default:
$date1 = $dates['value']['formatted'];
$date2 = $dates['value2']['formatted'];
break;
}
// Pull the timezone, if any, out of the formatted result and tack it
// back on at the end, if it is in the current formatted date.
$timezone = $dates['value']['formatted_timezone'];
if ($timezone) {
$timezone = ' ' . $timezone;
}
$date1 = str_replace($timezone, '', $date1);
$date2 = str_replace($timezone, '', $date2);
// No date values, display nothing.
if (empty($date1) && empty($date2)) {
$output .= '';
}
// From and To dates match or there is no To date, display a complete single date.
elseif ($date1 == $date2 || empty($date2)) {
$output .= theme('date_display_single', $date1, $timezone);
}
// Same day, different times, don't repeat the date but show both From and To times.
elseif (date_has_time($field['granularity']) && $dates['value']['formatted_date'] == $dates['value2']['formatted_date']) {
// Replace the original time with the from/to time in the formatted start date.
// Make sure that parentheses or brackets wrapping the time will be retained in the
// final result.
$time1 = preg_replace('`^([\(\[])`', '', $dates['value']['formatted_time']);
$time1 = preg_replace('([\)\]]$)', '', $time1);
$time2 = preg_replace('`^([\(\[])`', '', $dates['value2']['formatted_time']);
$time2 = preg_replace('([\)\]]$)', '', $time2);
$time = theme('date_display_range', $time1, $time2);
$replaced = str_replace($time1, $time, $date1);
$output .= theme('date_display_single', $replaced, $timezone);
}
// Different days, display both in their entirety.
else {
$output .= theme('date_display_range', $date1, $date2, $timezone);
}
return $output;
}
function theme_date_display_single($date, $timezone = NULL) {
return '<span class="date-display-single">'. $date . $timezone .'</span>';
}
function theme_date_display_range($date1, $date2, $timezone = NULL) {
return '<span class="date-display-start">'. $date1 .'</span>'.
'<span class="date-display-separator"> - </span>' .
'<span class="date-display-end">'. $date2 . $timezone. '</span>';
}
/**
* Theme a format interval for a date element
*
* @param $field = the field settings
* @param $node = node information, this is not always available and not
* always the full node, it depends on what value was provided to the formatter.
* Only the nid is always guaranteed to be available.
* @param $dates - an array of date information, see explanation for date_field_object for details.
* @return a formatted display
*
*/
function theme_date_format_interval($element) {
$node = $element['#node'];
$field_name = $element['#field_name'];
$context = !empty($node->content) ? $node->content[$field_name]['#context'] : 'full';
$type_name = $element['#type_name'];
$fields = content_fields();
$field = $fields[$field_name];
$item = $element['#item'];
// Get the formatter settings, either the default settings for this node
// type or the View settings stored in $node->date_info.
$options = date_formatter_get_settings($field_name, $type_name, $context);
if (!empty($node->date_info) && !empty($node->date_info->formatter_settings)) {
$options = $node->date_info->formatter_settings;
}
// If date_id is set for this field and the delta doesn't match, don't display it.
if (!empty($node->date_id)) {
foreach ((array) $node->date_id as $key => $id) {
list($module, $nid, $field_name, $delta, $other) = explode('.', $id);
if ($field_name == $field['field_name'] && isset($item['#delta']) && $delta != $item['#delta']) {
return;
}
}
}
// If this is not coming from Views, it is the full node.
// If we aren't retrieving a specific value, adjust the node values
// to match the formatter settings, removing values we should not see.
if (!empty($node->content) && empty($node->date_id)) {
$node = date_prepare_node($node, $field, $type_name, $context, $options);
// Did the current value get removed by formatter settings?
if (empty($node->{$field_name}[$item['#delta']])) {
return;
}
// Adjust the $element values to match the changes.
$element['#node'] = $node;
}
$dates = date_formatter_process($element);
return theme('date_time_ago', $dates['value']['local']['object'], $dates['value2']['local']['object']);
}
/**
* Theme the human-readable description for a Date Repeat rule.
*
* TODO -
* add in ways to store the description in the date so it isn't regenerated
* over and over and find a way to allow description to be shown or hidden.
*/
function theme_date_repeat_display($field, $item, $node = NULL) {
$output = '';
if (!empty($item['rrule'])) {
$output = date_repeat_rrule_description($item['rrule']);
$output = '<div>'. $output .'</div>';
}
return $output;
}
/**
* Adjust from/to date format to account for 'all day'.
*
* @param array $field, the field definition for this date field.
* @param string $which, which value to return, 'date1' or 'date2'.
* @param object $date1, a date/time object for the 'from' date.
* @param object $date2, a date/time object for the 'to' date.
* @param string $format
* @param object $node, the node this date comes from (may be incomplete, always contains nid).
* @param object $view, the view this node comes from, if applicable.
* @return formatted date.
*/
function theme_date_all_day($field, $which, $date1, $date2, $format, $node, $view = NULL) {
if (empty($date1) || !is_object($date1) || $format == 'format_interval') {
return;
}
if (empty($date2)) {
$date2 = $date1;
}
$suffix = '';
if (!date_has_time($field['granularity'])) {
$format = date_limit_format($format, array('year', 'month', 'day'));
}
else {
$format_granularity = date_format_order($format);
$format_has_time = FALSE;
if (in_array('hour', $format_granularity)) {
$format_has_time = TRUE;
}
$all_day = date_field_all_day($field, $date1, $date2);
if ($all_day && $format_has_time) {
$format = date_limit_format($format, array('year', 'month', 'day'));
$suffix = ' ' . theme('date_all_day_label');
}
}
return trim(date_format_date($$which, 'custom', $format) . $suffix);
}
/**
* Theme the way an 'all day' label will look.
*/
function theme_date_all_day_label() {
return '('. date_t('All day', 'datetime') .')';
}
/** @} End of addtogroup themeable */

View file

@ -0,0 +1,18 @@
<?php
/**
* Implementation of hook_views_handlers().
*/
function date_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'date'),
),
// A date-specific handler for grouping multiple values.
'handlers' => array(
'date_handler_field_multiple' => array(
'parent' => 'content_handler_field_multiple',
),
),
);
}

View file

@ -0,0 +1,87 @@
<?php
/**
* Implementation of hook_views_convert().
*/
function date_views_convert($display, $type, &$view, $field, $id = NULL) {
static $date_fields;
if (!isset($date_fields)) {
$date_fields = array();
$content_types = content_types();
foreach ($content_types as $content_type) {
foreach ($content_type['fields'] as $content_field) {
if ($content_field['module'] == 'date') {
$date_fields["$content_field[field_name]_value_default"] = 'default';
$date_fields["$content_field[field_name]_value_year"] = 'year';
$date_fields["$content_field[field_name]_value_month"] = 'month';
$date_fields["$content_field[field_name]_value_day"] = 'day';
$date_fields["$content_field[field_name]_value_to|default"] = 'to|default';
$date_fields["$content_field[field_name]_value_to|year"] = 'to|year';
$date_fields["$content_field[field_name]_value_to|month"] = 'to|month';
$date_fields["$content_field[field_name]_value_to|day"] = 'to|day';
}
}
}
}
switch ($type) {
case 'filter':
if (isset($date_fields[$field['field']])) {
$item = $view->get_item($display, 'filter', $id);
switch ($date_fields[$field['field']]) {
case 'default':
$field_name = drupal_substr($field['field'], 0, -14);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value');
$item['granularity'] = 'second';
break;
case 'year':
$field_name = drupal_substr($field['field'], 0, -11);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value');
$item['granularity'] = 'year';
break;
case 'month':
$field_name = drupal_substr($field['field'], 0, -12);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value');
$item['granularity'] = 'month';
break;
case 'day':
$field_name = drupal_substr($field['field'], 0, -10);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value');
$item['granularity'] = 'day';
break;
case 'to|default':
$field_name = drupal_substr($field['field'], 0, -17);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value2');
$item['granularity'] = 'second';
break;
case 'to|year':
$field_name = drupal_substr($field['field'], 0, -14);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value2');
$item['granularity'] = 'year';
break;
case 'to|month':
$field_name = drupal_substr($field['field'], 0, -15);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value2');
$item['granularity'] = 'month';
break;
case 'to|day':
$field_name = drupal_substr($field['field'], 0, -13);
$item['date_fields'] = array('node_data_'. $field_name .'.'. $field_name .'_value2');
$item['granularity'] = 'day';
break;
}
$item['table'] = 'node';
$item['field'] = 'date_filter';
$item['form_type'] = 'date_text';
$item['operator'] = $field['operator'] == '<>' ? '!=' : $field['operator'];
if ($field['value'] == 'now' || $field['options'] == 'now') {
$item['default_date'] = 'now';
}
else {
$item['value'] = $field['value'];
$item['default_date'] = $field['options'];
}
$view->set_item($display, 'filter', $id, $item);
}
break;
}
}

View file

@ -0,0 +1,717 @@
<?php
/**
* @file
* Date administration code.
* Moved to separate file since there is a lot of code here that is not needed often.
*/
/**
* Implementation of hook_widget_settings().
*/
function _date_widget_settings($op, &$field) {
switch ($op) {
case 'callbacks':
return array('default value' => CONTENT_CALLBACK_CUSTOM);
case 'form':
return date_widget_settings_form($field);
case 'save':
return array('default_value', 'default_value_code', 'default_value2', 'default_value_code2', 'input_format', 'input_format_custom', 'increment', 'text_parts', 'year_range', 'label_position');
case 'validate':
if ($field['default_value'] == 'strtotime') {
$is_strtotime = @strtotime($field['default_value_code']);
if (!$is_strtotime) {
form_set_error('default_value_code', t('The Strtotime default value is invalid.'));
}
}
if ($field['default_value2'] == 'strtotime') {
$is_strtotime = @strtotime($field['default_value_code2']);
if (!$is_strtotime) {
form_set_error('default_value_code2', t('The Strtotime default value for the To Date is invalid.'));
}
}
if (in_array($field['widget']['type'], array('date_select', 'date_popup', 'date_select_repeat', 'date_popup_repeat')) && !date_range_valid($field['year_range'])) {
form_set_error('year_range', t('Years back and forward must be in the format -9:+9.'));
}
break;
}
}
/**
* Custom widget settings manipulation.
*
* CCK widget settings can't use form_set_value(),
* so do it in a custom function.
*/
function date_widget_settings_validate(&$form, &$form_state) {
// TODO Not sure there is any limitation any more in this version. Need to check.
if ($form_state['values']['widget_type'] == 'date_popup') {
// Only a limited set of formats is available for the Date Popup module
if (!empty($form_state['values']['input_format_custom']) || !in_array($form_state['values']['input_format'], date_popup_formats())) {
form_set_value($form['input_format_custom'], NULL, $form_state);
form_set_value($form['input_format'], DATE_FORMAT_DATETIME, $form_state);
}
}
if (isset($form_state['values']['advanced']['label_position'])) {
form_set_value($form['label_position'], $form_state['values']['advanced']['label_position'], $form_state);
}
// Munge the table display for text parts back into an array of text parts.
if (is_array($form_state['values']['advanced']['text_parts'])) {
form_set_value($form['text_parts'], array_keys(array_filter($form_state['values']['advanced']['text_parts'])), $form_state);
}
}
function date_widget_settings_form($widget) {
$form = array(
'#element_validate' => array('date_widget_settings_validate'),
);
$form['input']['default_value'] = array(
'#type' => 'select', '#title' => t('Default value'),
'#default_value' => !empty($widget['default_value']) ? $widget['default_value'] : 'blank',
'#options' => array('blank' => t('Blank'), 'now' => t('Now'), 'strtotime' => t('Relative')),
'#description' => t("A default value to use for this field. If you select 'Relative', add details below."),
);
$form['input']['default_value2'] = array(
'#type' => 'select', '#title' => t('Default value for To date'),
'#default_value' => !empty($widget['default_value2']) ? $widget['default_value2'] : 'same',
'#options' => array('same' => t('Same as From date'), 'blank' => t('Blank'), 'now' => t('Now'), 'strtotime' => t('Relative')),
'#description' => t("A default value to use for this field. If you select 'Relative', add details below."),
);
$form['input']['default'] = array(
'#type' => 'fieldset',
'#title' => t('Customize Default Value'),
'#description' => '<p>' . t("The custom value for a Relative default should be something that describes a time by reference to the current day using strtotime, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See !strtotime for more details.", array('!strtotime' => l(t('strtotime'), 'http://www.php.net/manual/en/function.strtotime.php'))) . '</p>',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['input']['default']['default_value_code'] = array(
'#type' => 'textfield', '#title' => t('Custom value for From date'),
'#default_value' => isset($widget['default_value_code']) ? $widget['default_value_code'] : '',
);
$form['input']['default']['default_value_code2'] = array(
'#type' => 'textfield', '#title' => t('Custom value for To date'),
'#default_value' => !empty($widget['default_value_code2']) ? $widget['default_value_code2'] : '',
);
$options = array();
if ($widget['type'] == 'date_popup' && module_exists('date_popup')) {
$formats = date_popup_formats();
$default_format = array_shift($formats);
}
else {
// example input formats must show all possible date parts, so add seconds.
$formats = str_replace('i', 'i:s', array_keys(date_get_formats('short')));
$formats = drupal_map_assoc($formats);
$default_format = str_replace('i', 'i:s', variable_get('date_format_short', 'm/d/Y - H:i'));
}
$now = date_example_date();
foreach ($formats as $f) {
$options[$f] = date_format_date($now, 'custom', $f);
}
$form['input']['input_format'] = array(
'#type' => 'select', '#title' => t('Input format'),
'#default_value' => !empty($widget['input_format']) ? $widget['input_format'] : $default_format,
'#options' => $options,
'#description' => t('Set the order and format for the date parts in the input form. The format will be adapted to remove values not in the granularity for this field.'),
);
// Only a limited set of formats is available for the Date Popup module
if ($widget['type'] != 'date_popup') {
$form['input']['format']['input_format_custom'] = array(
'#type' => 'textfield', '#title' => t('Custom input format'),
'#default_value' => !empty($widget['input_format_custom']) ? $widget['input_format_custom'] : '',
'#description' => t("The custom format, if provided, will override the input format selected above. The custom format, if provided, will override the selected display or input options. Define a php date format string like 'm-d-Y H:i' (see <a href=\"@link\">http://php.net/date</a> for more details).", array('@link' => 'http://php.net/date')),
);
}
else {
$form['input']['format']['input_format_custom'] = array(
'#type' => 'hidden',
'#value' => '',
);
}
if (in_array($widget['type'], array('date_select', 'date_popup', 'date_select_repeat', 'date_popup_repeat'))) {
$form['input']['year_range'] = array(
'#type' => 'textfield',
'#title' => t('Years back and forward'),
'#default_value' => !empty($widget['year_range']) ? $widget['year_range'] : '-3:+3',
'#size' => 10,
'#maxsize' => 10,
'#description' => t('Number of years to go back and forward in the year selection list, default is -3:+3.'),
);
$form['input']['increment'] = array(
'#type' => 'select', '#title' => t('Time increment'),
'#default_value' => isset($widget['increment']) ? $widget['increment'] : 1,
'#options' => array(1 => 1, 5 => 5, 10 => 10, 15 => 15, 30 => 30),
'#description' => t('Increment the minute and second fields by this amount.'),
);
}
else {
$form['increment'] = array(
'#type' => 'hidden',
'#value' => !empty($widget['increment']) ? $widget['increment'] : 1,
);
$form['year_range'] = array(
'#type' => 'hidden',
'#value' => isset($widget['year_range']) ? $widget['year_range'] : '-3:+3',
);
}
$form['label_position'] = array(
'#type' => 'value',
'#value' => !empty($widget['label_position']) ? $widget['label_position'] : 'above',
);
$form['text_parts'] = array(
'#type' => 'value',
'#value' => isset($widget['text_parts']) ? $widget['text_parts'] : '',
);
$form['input']['advanced'] = array(
'#tree' => TRUE,
'#type' => 'fieldset',
'#title' => t('Customize Date Parts'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['input']['advanced']['label_position'] = array(
'#type' => 'radios',
'#options' => array('above' => t('Above'), 'within' => t('Within'), 'none' => t('None')),
'#default_value' => !empty($widget['label_position']) ? $widget['label_position'] : 'above',
'#title' => t('Position of date part labels'),
'#description' => t("The location of date part labels, like 'Year', 'Month', or 'Day'. 'Above' will display them as titles above each date part. 'Within' will insert the label as the first option in the select list and in blank textfields. 'None' will not label any of the date parts. The exact text in the label is controlled by themes like 'date_part_label_year' and 'date_part_label_month'."),
);
$form['input']['advanced']['text_parts'] = array(
'#theme' => $widget['type'] == 'date_select' ? 'date_text_parts' : '',
);
$text_parts = isset($widget['text_parts']) ? (array) $widget['text_parts'] : array();
foreach (date_granularity_names() as $key => $value) {
if ($widget['type'] == 'date_select') {
$form['input']['advanced']['text_parts'][$key] = array(
'#type' => 'radios',
'#default_value' => in_array($key, $text_parts) ? 1 : 0,
'#options' => array(0 => '', 1 => ''),
);
}
else {
$form['input']['advanced']['text_parts'][$key] = array(
'#type' => 'hidden',
'#value' => in_array($key, (array) $widget['text_parts']) ? 1 : 0,
);
}
}
return $form;
}
/**
* Display the text/select options for date parts in a table
* for easier readability.
*/
function theme_date_text_parts($element) {
$names = date_granularity_names();
$rows = array();
foreach ($names as $key => $part) {
if ($element[$key]['#type'] == 'hidden') {
$rows[] = drupal_render($element[$key]);
}
else {
$rows[] = array($names[$key], drupal_render($element[$key][0]), drupal_render($element[$key][1]));
}
}
if ($element['year']['#type'] == 'hidden') {
return implode($rows);
}
else {
$header = array(t('Input Type'), t('Select list'), t('Text field'));
return theme('table', $header, $rows);
}
}
/**
* Implementation of hook_field_settings().
*/
function _date_field_settings($op, $field) {
switch ($op) {
case 'form':
return date_field_settings_form($field);
case 'validate':
if (!in_array('year', $field['granularity'])) {
form_set_error('granularity', t('Granularity must include a year.'));
}
if ($field['tz_handling'] != 'none' && !in_array('hour', array_filter($field['granularity']))) {
form_set_error('tz_handling', t('Dates without hours granularity must not use any timezone handling.'));
}
break;
case 'save':
$options = array('granularity', 'timezone_db', 'tz_handling', 'todate', 'repeat', 'repeat_collapsed', 'default_format');
return $options;
case 'database columns':
return date_columns($field);
case 'views data':
$data = content_views_field_views_data($field);
$db_info = content_database_info($field);
$table_alias = content_views_tablename($field);
// Swap in the date multiple value handler for
// CCK's regular multiple value handler.
$data[$table_alias][$field['field_name'] .'_value']['field']['handler'] = 'date_handler_field_multiple';
// Unset the filter and argument handlers, dates can use the generic
// date argument and filter handlers created by the Date API.
//unset($data[$table_alias][$field['field_name'] .'_value']['argument']);
//unset($data[$table_alias][$field['field_name'] .'_value']['filter']);
$data[$table_alias][$field['field_name'] .'_value']['argument']['handler'] = 'date_api_argument_handler';
$data[$table_alias][$field['field_name'] .'_value']['filter']['handler'] = 'date_api_filter_handler';
// Add in another set of fields for the To date.
if (!empty($field['todate'])) {
$data[$table_alias][$field['field_name'] .'_value']['field']['title'] = $data[$table_alias][$field['field_name'] .'_value']['title'];
$data[$table_alias][$field['field_name'] .'_value']['field']['title short'] = $data[$table_alias][$field['field_name'] .'_value']['title short'];
$data[$table_alias][$field['field_name'] .'_value2'] = $data[$table_alias][$field['field_name'] .'_value'];
$data[$table_alias][$field['field_name'] .'_value']['title'] .= ' - ' . t('From date');
$data[$table_alias][$field['field_name'] .'_value']['title short'] .= ' - ' . t('From date');
$data[$table_alias][$field['field_name'] .'_value']['field']['title'] .= ' - ' . t('From date');
$data[$table_alias][$field['field_name'] .'_value']['field']['title short'] .= ' - ' . t('From date');
$data[$table_alias][$field['field_name'] .'_value2']['title'] .= ' - ' . t('To date');
$data[$table_alias][$field['field_name'] .'_value2']['title short'] .= ' - ' . t('To date');
$data[$table_alias][$field['field_name'] .'_value2']['field']['title'] .= ' - ' . t('To date');
$data[$table_alias][$field['field_name'] .'_value2']['field']['title short'] .= ' - ' . t('To date');
$data[$table_alias][$field['field_name'] .'_value2']['field']['field'] .= '2';
$data[$table_alias][$field['field_name'] .'_value2']['sort']['field'] .= '2';
}
return $data;
}
}
/**
* Callback for field columns.
*/
function date_columns($field) {
if ($field['type'] == 'date') {
$db_columns['value'] = array('type' => 'varchar', 'length' => 20, 'not null' => FALSE, 'sortable' => TRUE, 'views' => TRUE);
}
elseif ($field['type'] == 'datestamp') {
$db_columns['value'] = array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE, 'views' => TRUE);
}
elseif ($field['type'] == 'datetime') {
$db_columns['value'] = array('type' => 'datetime', 'not null' => FALSE, 'sortable' => TRUE, 'views' => TRUE);
}
// If a second date is needed for 'To date', just make a copy of the first one.
if (!empty($field['todate'])) {
$db_columns['value2'] = $db_columns['value'];
// We don't want CCK to create additional columns, just the first.
// We modify them our own way in views data.
$db_columns['value2']['views'] = FALSE;
}
// timezone and offset columns are used only if date-specific dates are chosen.
if (isset($field['tz_handling']) && $field['tz_handling'] == 'date') {
$db_columns['timezone'] = array('type' => 'varchar', 'length' => 50, 'not null' => FALSE, 'sortable' => TRUE, 'views' => FALSE);
$db_columns['offset'] = array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE, 'views' => FALSE);
if (!empty($field['todate'])) $db_columns['offset2'] = array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE, 'views' => FALSE);
}
if (isset($field['repeat']) && $field['repeat'] == 1) {
$db_columns['rrule'] = array('type' => 'text', 'not null' => FALSE, 'sortable' => FALSE, 'views' => FALSE);
}
return $db_columns;
}
function date_field_settings_form($field) {
$form = array(
'#element_validate' => array('date_field_settings_validate'),
);
// Make sure granularity is in the right format and has no empty values.
if (!empty($field['granularity']) && is_array($field['granularity'])) {
$granularity = array_filter($field['granularity']);
}
else {
$granularity = array('year', 'month', 'day', 'hour', 'minute');
}
$tz_handling = isset($field['tz_handling']) ? $field['tz_handling'] : (date_has_time($granularity) ? 'site' : 'none');
// If adding a repeat, override the Content module's handling of the multiple values option.
if (module_exists('date_repeat') && date_is_repeat_field($field)) {
$form['multiple'] = array('#type' => 'hidden', '#value' => 1);
$form['repeat'] = array('#type' => 'hidden', '#value' => 1);
}
else {
$form['repeat'] = array('#type' => 'hidden', '#value' => 0);
}
$description = t("Display a matching second date field as a 'To date'. If marked 'Optional' field will be presented but not required. If marked 'Required' the 'To date' will be required if the 'From date' is required or filled in.");
$description .= date_data_loss_warning('To date');
$form['input']['todate'] = array(
'#type' => 'radios', '#title' => t('To Date'),
'#options' => array('' => t('Never'), 'optional' => t('Optional'), 'required' => t('Required')),
'#description' => $description,
'#default_value' => isset($field['todate']) ? $field['todate'] : '',
);
$form['input']['granularity'] = array(
'#type' => 'select', '#title' => t('Granularity'),
'#default_value' => $granularity,
'#options' => date_granularity_names(),
'#multiple' => TRUE,
'#description' => t('Set the date elements to be stored (at least a year is required).'),
);
$format_types = array();
foreach (date_get_format_types('', TRUE) as $name => $info) {
$format_types[$name] = $info['title'];
}
$form['default_format'] = array(
'#type' => 'select',
'#title' => t('Default Display'),
'#default_value' => !empty($field['default_format']) ? $field['default_format'] : 'medium',
'#options' => $format_types,
'#description' => t('Select a default format type to be used for the date display. Visit the <a href="@date-time-page">Date and time date format page</a> to add and edit format types.', array('@date-time-page' => url('admin/settings/date-time/formats'))),
);
$description = t('Select the timezone handling method to be used for this date field.');
$description .= date_data_loss_warning('Time zone handling');
$form['tz_handling'] = array(
'#type' => 'select',
'#title' => t('Time zone handling'),
'#default_value' => $tz_handling,
'#options' => date_timezone_handling_options(),
'#description' => $description,
);
// Force this value to hidden because we don't want to allow it to be changed right now,
// but allow it to be a variable if needed.
$form['timezone_db'] = array(
'#type' => 'hidden',
'#value' => date_get_timezone_db($tz_handling),
);
if (module_exists('date_repeat') && date_is_repeat_field($field)) {
$form['repeat_collapsed'] = array(
'#type' => 'radios',
'#default_value' => !empty($field['repeat_collapsed']) ? intval($field['repeat_collapsed']) : 0,
'#options' => array(0 => t('Expanded'), 1 => t('Collapsed')),
'#title' => t('Repeat display'),
'#description' => t("Should the repeat options form start out expanded or collapsed? Set to 'Collapsed' to make those options less obtrusive."),
);
}
return $form;
}
/**
* Custom field settings manipulation.
*
* CCK field settings can't use form_set_value(),
* so do it in a custom function.
*/
function date_field_settings_validate(&$form, &$form_state) {
if ($form_state['values']['tz_handling'] == 'none') {
form_set_value($form['timezone_db'], '', $form_state);
}
else {
form_set_value($form['timezone_db'], date_get_timezone_db($form_state['values']['tz_handling']), $form_state);
}
}
function date_data_loss_warning($name) {
return '<p class="error">' . t('Changing the %name setting after data has been created could result in the loss of data!', array('%name' => $name)) . '</p>';
}
/**
* Timezone handling options
*
* the 'none' option will do no timezone conversions and will store and display dates exactly as entered
* useful in locales or situations where timezone conversions are not working reliably,
* for dates with no times, for historical dates where timezones are irrelevant,
* or anytime conversion is unnecessary or undesirable
*/
function date_timezone_handling_options() {
return array(
'site' => t("Site's time zone"),
'date' => t("Date's time zone"),
'user' => t("User's time zone"),
'utc' => 'UTC',
'none' => t('No time zone conversion'),
);
}
/**
* Get an example date and make sure the difference between
* month and day and 12 and 24 hours will be clear.
*/
function date_example_date() {
$now = date_now();
if (date_format($now, 'm') == date_format($now, 'd')) {
date_modify($now, '+1 day');
}
if (date_format($now, 'H') == date_format($now, 'h')) {
date_modify($now, '+12 hours');
}
return $now;
}
/**
* Helper function to create a field array with default values for a date
* $field, based on field type, widget type, and timezone handling.
*
* This function can be used to make it easier to create new date fields
* in profiles or custom code.
*
* @param $field_type
* Can be 'date', 'datestamp', or 'datetime'.
* @param $widget_type
* Can be 'date_text', 'date_text_repeat', 'date_select',
* 'date_select_repeat', 'date_popup', 'date_popup_repeat'.
* @param $tz_handling
* Can be 'site', 'date', 'utc', 'none', or 'user'.
* @param $overrides
* An optional array of field/widget values that should
* override the normal defaults.
*/
function date_field_default_values($field_type, $widget_type, $tz_handling, $overrides = array()) {
module_load_include('inc', 'content', 'includes/content.crud');
$repeat_widgets = array('date_select_repeat', 'date_text_repeat', 'date_popup_repeat');
$base_field_values = array(
'field_name' => '',
'type_name' => '',
'multiple' => in_array($widget_type, $repeat_widgets) ? 1 : 0,
'module' => 'date',
'granularity' => array(
'year' => 'year',
'month' => 'month',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
),
'timezone_db' => date_get_timezone_db($tz_handling),
'tz_handling' => $tz_handling,
'todate' => 'optional',
'repeat' => in_array($widget_type, $repeat_widgets) ? 1 : 0,
'repeat_collapsed' => 0,
'default_format' => 'medium',
);
$base_widget_values = array(
'label' => '',
'default_value' => 'now',
'default_value_code' => '',
'default_value2' => 'same',
'default_value_code2' => '',
'input_format' => date_default_format($widget_type),
'input_format_custom' => '',
'increment' => 1,
'text_parts' => array(),
'year_range' => '-3:+3',
'label_position' => 'above',
'weight' => 0,
'description' => '',
'module' => 'date',
);
$field_values['date'] = $base_field_values;
$field_values['date']['type'] = 'date';
$field_values['datestamp'] = $base_field_values;
$field_values['datestamp']['type'] = 'datestamp';
$field_values['datetime'] = $base_field_values;
$field_values['datetime']['type'] = 'datetime';
$widget_values['date_select'] = $base_widget_values;
$widget_values['date_select']['type'] = 'date_select';
$widget_values['date_text'] = $base_widget_values;
$widget_values['date_text']['type'] = 'date_text';
$widget_values['date_popup'] = $base_widget_values;
$widget_values['date_popup']['type'] = 'date_popup';
$widget_values['date_popup']['module'] = 'date_popup';
$widget_values['date_select_repeat'] = $base_widget_values;
$widget_values['date_select_repeat']['type'] = 'date_select_repeat';
$widget_values['date_text_repeat'] = $base_widget_values;
$widget_values['date_text_repeat']['type'] = 'date_text_repeat';
$widget_values['date_popup_repeat'] = $base_widget_values;
$widget_values['date_popup_repeat']['type'] = 'date_popup_repeat';
// Get the basic field array with content module default values.
$field = content_field_default_values($field_type);
// Update it with default values for this date field and widget type.
foreach ($field_values[$field_type] as $key => $value) {
$field[$key] = $value;
}
foreach ($widget_values[$widget_type] as $key => $value) {
$field['widget'][$key] = $value;
}
// Allow overrides of specific default values.
foreach ($overrides as $key => $value) {
if ($key != 'widget') {
$field[$key] = $value;
}
else {
foreach ($value as $widget_key => $widget_value) {
$field['widget'][$widget_key] = $widget_value;
}
}
}
// Make sure multiple gets set correctly for repeating dates.
if ($field['repeat']) $field['multiple'] = 1;
// Reset columns and db_storage, which may need to be
// adjusted to the new values.
$field['columns'] = date_columns($field);
$field['db_storage'] = content_storage_type($field);
return $field;
}
/**
* Formatter settings.
*
* Form element used both in the date_handler_field_multiple Views handler
* and on the CCK Display fields page.
*/
function _date_formatter_settings($form_state = NULL, $field, $options = array(), $views_form = FALSE) {
$field_name = $field['field_name'];
$field = content_fields($field_name);
$type_name = isset($options['type_name']) ? $options['type_name'] : $field['type_name'];
$context = isset($options['context']) ? $options['context'] : 'full';
if (empty($options['fromto'])) {
$options = date_formatter_get_settings($field_name, $type_name, $context);
}
$form = array();
$form['fromto'] = array(
'#access' => $field['todate'],
'#weight' => 5,
);
if (isset($options['fromto']) && isset($options['fromto']['fromto'])) {
$default = $options['fromto']['fromto'];
}
else {
$default = 'both';
}
$form['fromto']['fromto'] = array(
'#type' => 'select',
'#options' => array(
'both' => t('Display From and To dates'),
'value' => t('Display From date only'),
'value2' => t('Display To date only'),
),
'#default_value' => $default,
'#weight' => 1,
);
$form['multiple'] = array(
'#access' => $field['multiple'],
'#weight' => 6,
);
// Make the string translatable by keeping it as a whole rather than
// translating prefix and suffix separately.
if (isset($options['multiple']) && isset($options['multiple']['multiple_number'])) {
$default = $options['multiple']['multiple_number'];
}
else {
$default = '';
}
list($prefix, $suffix) = explode('@count', t('Show @count value(s)'));
$form['multiple']['multiple_number'] = array(
'#type' => 'textfield',
'#size' => 5,
'#field_prefix' => theme('advanced_help_topic', 'date_api', 'date-display') . $prefix,
'#field_suffix' => $suffix,
'#default_value' => $default,
'#weight' => 1,
);
if ($views_form) {
$form['multiple']['multiple_number'] += array(
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
);
}
if (isset($options['multiple']) && isset($options['multiple']['multiple_from'])) {
$default = $options['multiple']['multiple_from'];
}
else {
$default = '';
}
list($prefix, $suffix) = explode('@count', t('starting from @count'));
$form['multiple']['multiple_from'] = array(
'#type' => 'textfield',
'#size' => 15,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $default,
'#weight' => 2,
);
if ($views_form) {
$form['multiple']['multiple_from'] += array(
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
);
}
if (isset($options['multiple']) && isset($options['multiple']['multiple_to'])) {
$default = $options['multiple']['multiple_to'];
}
else {
$default = '';
}
list($prefix, $suffix) = explode('@count', t('ending on @count'));
$form['multiple']['multiple_to'] = array(
'#type' => 'textfield',
'#size' => 15,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $default,
'#weight' => 3,
);
if ($views_form) {
$form['multiple']['multiple_to'] += array(
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
);
}
$form['repeat'] = array(
'#access' => $field['repeat'],
'#weight' => 7,
);
if (isset($options['repeat']) && isset($options['repeat']['show_repeat_rule'])) {
$default = $options['repeat']['show_repeat_rule'];
}
else {
$default = 'show';
}
$form['repeat']['show_repeat_rule'] = array(
'#type' => 'select',
'#options' => array(
'show' => t('Display repeat rule'),
'hide' => t('Hide repeat rule')),
'#default_value' => $default,
);
if (!$views_form) {
$form['field'] = array(
'#type' => 'value',
'#value' => $field,
);
$form['type_name'] = array(
'#type' => 'value',
'#value' => $type_name,
);
$form['context'] = array(
'#type' => 'value',
'#value' => $context,
);
}
return $form;
}

View file

@ -0,0 +1,185 @@
<?php
/**
* Implementation of Devel module's hook_content_generate().
*
* Included only when needed.
*/
function _date_content_generate($node, $field) {
include_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc');
$node_field = array();
if (isset($field['widget']['year_range'])) {
$split = explode(':', $field['widget']['year_range']);
$back = str_replace('-', '', $split[0]);
$forward = str_replace('+', '', $split[1]);
}
else {
$back = 2;
$forward = 2;
}
// Pick a random year within the time range,
// and a random second within that year.
$year = date_format(date_now(), 'Y') - $back + mt_rand(0, ($forward + $back));
$start = date_make_date($year .'-01-01 00:00:00', date_get_timezone_db($field['tz_handling']));
$leap = date_format($start, 'L');
$max_days = $leap ? 366 : 365;
$seconds = mt_rand(0, ($max_days * 86400));
date_modify($start, "+$seconds seconds");
$increment = $field['widget']['increment'];
date_increment_round($start, $increment);
// Modify To date by 1 hour to 3 days, shorter for repeating dates
// longer for others.
$start2 = drupal_clone($start);
$max = !empty($field['repeat']) ? 720 : 4320;
$max = 240;
date_modify($start2, '+'. mt_rand(60, $max) .' minutes');
date_increment_round($start2, $increment);
if ($field['tz_handling'] == 'date') {
// Choose a random timezone.
// Not all keys exist, so we have to check.
$timezones = array_keys(date_timezone_names(TRUE));
$key = mt_rand(0, count($timezones) - 1);
if (!array_key_exists($key, $timezones)) {
$timezone = date_default_timezone_name();
}
else {
$timezone = $timezones[$key];
}
}
else {
$timezone = date_get_timezone($field['tz_handling']);
}
switch ($field['type']) {
case 'date':
$format = DATE_FORMAT_ISO;
break;
case 'datestamp':
$format = DATE_FORMAT_UNIX;
break;
case 'datetime':
$format = DATE_FORMAT_DATETIME;
break;
}
$node_field['value'] = date_format($start, $format);
if ($field['todate']) {
$node_field['value2'] = date_format($start2, $format);
}
date_timezone_set($start, timezone_open($timezone));
$node_field['timezone'] = $timezone;
$node_field['offset'] = date_offset_get($start);
date_timezone_set($start2, timezone_open($timezone));
$node_field['offset2'] = date_offset_get($start2);
if (!empty($field['repeat'])) {
include_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_calc.inc');
include_once('./'. drupal_get_path('module', 'date') .'/date_repeat.inc');
include_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
// Create a repeating date.
$duration = date_difference($start, $start2);
$form_values = array();
// Create the default case more frequently than case 1 or 2.
$which = mt_rand(0, 10);
$max_items = 10;
$intervals = array_keys(INTERVAL_options());
unset($intervals[0]);
$interval = $intervals[mt_rand(1, 3)];
switch ($which) {
case 1:
$mo = mt_rand(1, 28);
$options = array('YEARLY', 'MONTHLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
// Make sure we'll find a match in our range.
if ($freq == 'YEARLY') {
$interval = 1;
}
$form_values['BYMONTHDAY'] = array($mo);
break;
case 2:
$mo = mt_rand(1, 12);
$options = array('YEARLY', 'MONTHLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
// Make sure we'll find a match in our range.
if ($freq == 'YEARLY') {
$interval = 1;
}
$form_values['BYMONTH'] = array($mo);
break;
default:
$dows = array_keys(date_content_repeat_dow_options());
$day = date_content_generate_key($dows);
$dow = $dows[$day];
$options = array('MONTHLY', 'DAILY', 'WEEKLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
$form_values['BYDAY'] = array($dow);
break;
}
$form_values['INTERVAL'] = $interval;
switch ($freq) {
case 'YEARLY':
$period = 'year';
break;
case 'MONTHLY':
$period = 'month';
break;
case 'WEEKLY':
$period = 'week';
break;
default:
$period = 'day';
break;
}
date_modify($start2, '+'. max(1, $forward) .' years');
date_increment_round($start2, $increment);
$until = date_format($start2, 'Y-m-d H:i:s');
$form_values['UNTIL'] = array('datetime' => $until, 'tz' => 'UTC');
$form_values['COUNT'] = $max_items;
$rrule = date_api_ical_build_rrule($form_values);
$values = date_repeat_build_dates($rrule, $form_values, $field, $node_field);
$start = $node_field;
$node_field = array(0 => $start);
$node_field[0]['rrule'] = $rrule;
$node_field += $values;
}
return $node_field;
}
function date_content_generate_key($array) {
$keys = array_keys($array);
$min = array_shift($keys);
$max = array_pop($keys);
return mt_rand($min, $max);
}
/**
* Helper function for BYDAY options.
*
* Creates options like -1SU and 2TU
* Omit options that won't find many matches, like 5th Sunday.
*/
function date_content_repeat_dow_options() {
$options = array();
foreach (date_repeat_dow_count_options() as $count_key => $count_value) {
foreach (date_repeat_dow_day_options() as $dow_key => $dow_value) {
if ($count_key != 5 && $count_key != -5) {
$options[$count_key . $dow_key] = $count_value .' '. $dow_value;
}
}
}
return $options;
}

View file

@ -0,0 +1,586 @@
<?php
/**
* @file
* Date forms and form themes and validation.
*
* All code used in form editing and processing is in this file,
* included only during form editing.
*/
/**
* Private implementation of hook_field validate operation.
*/
function _date_field_validate($op, &$node, $field, &$items, $teaser, $page) {
$field_name = $field['field_name'];
// Don't try to validate if there were any errors before this point
// since the element won't have been munged back into a date.
if (!form_get_errors()) {
foreach ($items as $delta => $item) {
$process = date_process_values($field);
foreach ($process as $processed) {
$error_field = $field['field_name'] .']['. $delta .']['. $processed;
$error_field .= $field['widget']['type'] == 'date_select' ? '][year' : '';
if ($processed == 'value' && $field['todate']
&& !date_is_valid($item['value'], $field['type'], $field['granularity'])
&& (date_is_valid($item['value2'], $field['type'], $field['granularity']))) {
form_set_error($error_field, t("A 'From date' date is required for field %field %delta.", array('%delta' => $field['multiple'] ? intval($delta + 1) : '', '%field' => t($field['widget']['label']))));
}
if ($processed == 'value2'
&& $field['todate'] == 'required' && ($field['required']
&& date_is_valid($item['value'], $field['type'], $field['granularity'])
&& !date_is_valid($item['value2'], $field['type'], $field['granularity']))) {
form_set_error($error_field, t("A 'To date' is required for field %field %delta.", array('%delta' => $field['multiple'] ? intval($delta + 1) : '', '%field' => t($field['widget']['label']))));
}
}
}
}
}
/**
* Private implementation of hook_field update and insert operations.
*/
function _date_field_update($op, &$node, $field, &$items, $teaser, $page) {
$field_name = $field['field_name'];
if (empty($items)) {
//$node->$field_name = array(); // Not sure about this, CCK should handle it.
return;
}
$values = $items;
foreach ($values as $delta => $item) {
// Special case for ISO dates which may have been given artificial values for
// some date parts to make them into valid dates.
if (!empty($item['value']) && $field['type'] == DATE_ISO) {
$items[$delta]['value'] = date_limit_value($items[$delta]['value'], date_granularity($field), $field['type']);
if ($field['todate']) {
$items[$delta]['value2'] = date_limit_value($items[$delta]['value2'], date_granularity($field), $field['type']);
}
}
}
$node->$field['field_name'] = $items;
}
/**
* Private implementation of hook_widget().
*
* The widget builds out a complex date element in the following way:
*
* - A field is pulled out of the database which is comprised of one or
* more collections of from/to dates.
*
* - The dates in this field are all converted from the UTC values stored
* in the database back to the local time before passing their values
* to FAPI.
*
* - If values are empty, the field settings rules are used to determine
* if the default_values should be empty, now, the same, or use strtotime.
*
* - Each from/to combination is created using the date_combo element type
* defined by the date module. If the timezone is date-specific, a
* timezone selector is added to the first combo element.
*
* - If repeating dates are defined, a form to create a repeat rule is
* added to the field element.
*
* - The date combo element creates two individual date elements, one each
* for the from and to field, using the appropriate individual Date API
* date elements, like selects, textfields, or popups.
*
* - In the individual element validation, the data supplied by the user is
* used to update the individual date values.
*
* - In the combo date validation, the timezone is updated, if necessary,
* then the user input date values are used with that timezone to create
* date objects, which are used update combo date timezone and offset values.
*
* - In the field's submission processing, the new date values, which are in
* the local timezone, are converted back to their UTC values and stored.
*
*/
function _date_widget(&$form, &$form_state, &$field, $items, $delta = 0) {
// Be sure users that haven't run the update yet don't get
// a badly broken value.
if (!empty($field['repeat']) && !strstr($field['widget']['type'], '_repeat')) {
$field['multiple'] = 0;
return;
}
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc');
$timezone = date_get_timezone($field['tz_handling'], isset($items[0]['timezone']) ? $items[0]['timezone'] : date_default_timezone_name());
// TODO see if there's a way to keep the timezone element from ever being
// nested as array('timezone' => 'timezone' => value)). After struggling
// with this a while, I can find no way to get it displayed in the form
// correctly and get it to use the timezone element without ending up
// with nesting.
if (is_array($timezone)) {
$timezone = $timezone['timezone'];
}
// Convert UTC dates to their local values in DATETIME format,
// and adjust the default values as specified in the field settings.
// It would seem to make sense to do this conversion when the data
// is loaded instead of when the form is created, but the loaded
// field data is cached and we can't cache dates that have been converted
// to the timezone of an individual user, so we cache the UTC values
// instead and do our conversion to local dates in the form and
// in the formatters.
$process = date_process_values($field);
foreach ($process as $processed) {
if (!isset($items[$delta][$processed])) {
$items[$delta][$processed] = '';
}
$date = date_local_date($form, $form_state, $delta, $items[$delta], $timezone, $field, $processed);
$items[$delta][$processed] = is_object($date) ? date_format($date, DATE_FORMAT_DATETIME) : '';
}
$element = array(
'#type' => 'date_combo',
'#weight' => $delta,
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
'#date_timezone' => $timezone,
'#element_validate' => array('date_combo_validate', 'date_widget_validate'),
);
if ($field['tz_handling'] == 'date') {
$element['timezone'] = array(
'#type' => 'date_timezone',
'#delta' => $delta,
'#default_value' => $timezone,
'#weight' => $field['widget']['weight'] + .2,
);
}
// Add a date repeat form element, if needed.
if (module_exists('date_repeat') && $field['repeat'] == 1) {
require_once('./'. drupal_get_path('module', 'date') .'/date_repeat.inc');
_date_repeat_widget($element, $field, $items, $delta);
$element['rrule']['#weight'] = $field['widget']['weight'] + .4;
}
return $element;
}
/**
* Create local date object.
*
* Create a date object set to local time from the field and
* widget settings and item values, using field settings to
* determine what to do with empty values.
*/
function date_local_date($form, $form_state, $delta, $item, $timezone, $field, $part = 'value') {
if (!empty($form['nid']['#value'])) {
$default_value = '';
$default_value_code = '';
}
elseif ($part == 'value') {
$default_value = $field['widget']['default_value'];
$default_value_code = $field['widget']['default_value_code'];
}
else {
$default_value = $field['widget']['default_value2'];
$default_value_code = $field['widget']['default_value_code2'];
}
if (empty($item) || empty($item[$part])) {
if (empty($default_value) || $default_value == 'blank' || $delta > 0) {
return NULL;
}
elseif ($part == 'value2' && $default_value == 'same') {
if ($field['widget']['default_value'] == 'blank' || empty($item['value'])) {
return NULL;
}
else {
$date = date_make_date($item['value'], $timezone, DATE_DATETIME, $field['granularity']);
}
}
// Special case for 'now' when using dates with no timezone,
// make sure 'now' isn't adjusted to UTC value of 'now'.
elseif ($field['tz_handling'] == 'none') {
$date = date_now();
}
else {
$date = date_now($timezone);
}
}
else {
$value = $item[$part];
// Special case for ISO dates to create a valid date object for formatting.
if ($field['type'] == DATE_ISO) {
$value = date_fuzzy_datetime($value);
}
else {
$db_timezone = date_get_timezone_db($field['tz_handling']);
$value = date_convert($value, $field['type'], DATE_DATETIME, $db_timezone);
}
$date = date_make_date($value, date_get_timezone_db($field['tz_handling']), DATE_DATETIME, $field['granularity']);
if (empty($date)) {
return NULL;
}
date_timezone_set($date, timezone_open($timezone));
}
if (is_object($date) && empty($item[$part]) && $default_value == 'strtotime' && !empty($default_value_code)) {
date_modify($date, $default_value_code);
}
return $date;
}
/**
* Implementation of hook_elements().
*
* date_combo will create a 'from' and optional 'to' date, along with
* an optional 'timezone' column for date-specific timezones. Each
* 'from' and 'to' date will be constructed from date_select or date_text.
*/
function _date_elements() {
$type = array();
$type['date_combo'] = array(
'#input' => TRUE,
'#delta' => 0,
'#columns' => array('value', 'value2', 'timezone', 'offset', 'offset2'),
'#process' => array('date_combo_process'),
'#element_validate' => array('date_combo_validate'),
);
return $type;
}
/**
* Process an individual date element.
*/
function date_combo_process($element, $edit, &$form_state, $form) {
if (isset($element['#access']) && empty($element['#access'])) {
return $element;
}
$field_name = $element['#field_name'];
$field = $form['#field_info'][$field_name];
$delta = $element['#delta'];
$columns = $element['#columns'];
if (isset($columns['rrule'])) {
unset($columns['rrule']);
}
$from_field = 'value';
$to_field = 'value2';
$tz_field = 'timezone';
$offset_field = 'offset';
$offset_field2 = 'offset2';
if ($field['todate'] != 'required' && !empty($element['#default_value'][$to_field]) && $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) {
unset($element['#default_value'][$to_field]);
}
$element[$from_field] = array(
'#field' => $field,
'#title' => (!$field['multiple'] || $field['repeat']) ? t($field['widget']['label']) : '',
'#weight' => $field['widget']['weight'],
'#required' => ($field['required'] && $delta == 0) ? 1 : 0,
'#default_value' => isset($element['#value'][$from_field]) ? $element['#value'][$from_field] : '',
'#delta' => $delta,
'#date_timezone' => $element['#date_timezone'],
'#date_format' => date_limit_format(date_input_format($element, $field), $field['granularity']),
'#date_text_parts' => (array) $field['widget']['text_parts'],
'#date_increment' => $field['widget']['increment'],
'#date_year_range' => $field['widget']['year_range'],
'#date_label_position' => $field['widget']['label_position'],
);
$description = !empty($field['widget']['description']) ? t($field['widget']['description']) : '';
// Give this element the right type, using a Date API
// or a Date Popup element type.
switch ($field['widget']['type']) {
case 'date_select':
case 'date_select_repeat':
// From/to selectors with lots of parts will look better if displayed
// on two rows instead of in a single row.
if (!empty($field['todate']) && count($field['granularity']) > 3) {
$element[$from_field]['#attributes'] = array('class' => 'date-clear');
}
$element[$from_field]['#type'] = 'date_select';
break;
case 'date_popup':
case 'date_popup_repeat':
$element[$from_field]['#type'] = 'date_popup';
break;
default:
$element[$from_field]['#type'] = 'date_text';
break;
}
// If this field uses the 'To', add matching element
// for the 'To' date, and adapt titles to make it clear which
// is the 'From' and which is the 'To'.
if (!empty($field['todate'])) {
$element['#date_float'] = TRUE;
$element[$from_field]['#title'] = t('From date');
$element[$to_field] = $element[$from_field];
$element[$to_field]['#title'] = t('To date');
$element[$to_field]['#default_value'] = isset($element['#value'][$to_field]) ? $element['#value'][$to_field] : '';
$element[$to_field]['#required'] = FALSE;
$element[$to_field]['#weight'] += .1;
if (($field['widget']['type'] == 'date_select') && empty($description)) {
$description = t("Empty 'To date' values will use the 'From date' values.");
}
else {
if (trim($description) == "") {
$description = NULL;
}
}
$element['#fieldset_description'] = $description;
}
else {
$element[$from_field]['#description'] = $description;
}
// Create label for error messages that make sense in multiple values
// and when the title field is left blank.
if (!empty($field['multiple']) && empty($field['repeat'])) {
$element[$from_field]['#date_title'] = t('@field_name From date value #@delta', array('@field_name' => $field['widget']['label'], '@delta' => $delta + 1));
if (!empty($field['todate'])) {
$element[$to_field]['#date_title'] = t('@field_name To date value #@delta', array('@field_name' => $field['widget']['label'], '@delta' => $delta + 1));
}
}
elseif (!empty($field['todate'])) {
$element[$from_field]['#date_title'] = t('@field_name From date', array('@field_name' => $field['widget']['label']));
$element[$to_field]['#date_title'] = t('@field_name To date', array('@field_name' => $field['widget']['label']));
}
else {
$element[$from_field]['#date_title'] = $field['widget']['label'];
}
// Make sure field info will be available to the validator which
// does not get the values in $form.
$form_state['#field_info'][$field['field_name']] = $field;
return $element;
}
function date_element_empty($element, &$form_state) {
$item = array();
$item['value'] = NULL;
$item['value2'] = NULL;
$item['timezone'] = NULL;
$item['offset'] = NULL;
$item['offset2'] = NULL;
$item['rrule'] = NULL;
form_set_value($element, $item, $form_state);
return $item;
}
/**
* Validate and update a combo element.
* Don't try this if there were errors before reaching this point.
*/
function date_combo_validate($element, &$form_state) {
$form_values = $form_state['values'];
$field_name = $element['#field_name'];
$delta = $element['#delta'];
// If the whole field is empty and that's OK, stop now.
if (empty($element['#post'][$field_name]) && !$element['#required']) {
return;
}
// Repeating dates have a different form structure, so get the
// right item values.
$item = isset($form_values[$field_name]['rrule']) ? $form_values[$field_name] : $form_values[$field_name][$delta];
$posted = isset($form_values[$field_name]['rrule']) ? $element['#post'][$field_name] : $element['#post'][$field_name][$delta];
$field = $form_state['#field_info'][$element['#field_name']];
$from_field = 'value';
$to_field = 'value2';
$tz_field = 'timezone';
$offset_field = 'offset';
$offset_field2 = 'offset2';
// Unfortunately, due to the fact that much of the processing is already
// done by the time we get here, it is not possible highlight the field
// with an error, we just try to explain which element is creating the
// problem in the error message.
$parent = $element['#parents'];
$error_field = array_pop($parent);
$errors = array();
// Check for empty 'From date', which could either be an empty
// value or an array of empty values, depending on the widget.
$empty = TRUE;
if (!empty($item[$from_field])) {
if (!is_array($item[$from_field])) {
$empty = FALSE;
}
else {
foreach ($item[$from_field] as $key => $value) {
if (!empty($value)) {
$empty = FALSE;
break;
}
}
}
}
if ($empty) {
$item = date_element_empty($element, $form_state);
if (!$element['#required']) {
return;
}
}
// Don't look for further errors if errors are already flagged
// because otherwise we'll show errors on the nested elements
// more than once.
elseif (!form_get_errors()) {
// Check todate input for blank values and substitute in fromdate
// values where needed, then re-compute the todate with those values.
if ($field['todate']) {
$merged_date = array();
$to_date_empty = TRUE;
foreach ($posted[$to_field] as $part => $value) {
$to_date_empty = $to_date_empty && empty($value) && !is_numeric($value);
$merged_date[$part] = empty($value) && !is_numeric($value) ? $posted[$from_field][$part] : $value;
if ($part == 'ampm' && $merged_date['ampm'] == 'pm' && $merged_date['hour'] < 12) {
$merged_date['hour'] += 12;
}
elseif ($part == 'ampm' && $merged_date['ampm'] == 'am' && $merged_date['hour'] == 12) {
$merged_date['hour'] -= 12;
}
}
// If all date values were empty and a date is required, throw
// an error on the first element. We don't want to create
// duplicate messages on every date part, so the error will
// only go on the first.
if ($to_date_empty && $field['todate'] == 'required') {
$errors[] = t('Some value must be entered in the To date.');
}
$element[$to_field]['#value'] = $merged_date;
// Call the right function to turn this altered user input into
// a new value for the todate.
$item[$to_field] = $merged_date;
}
else {
$item[$to_field] = $item[$from_field];
}
$from_date = date_input_value($field, $element[$from_field]);
if (!empty($field['todate'])) {
$to_date = date_input_value($field, $element[$to_field]);
}
else {
$to_date = $from_date;
}
// Neither the from date nor the to date should be empty at this point
// unless they held values that couldn't be evaluated.
if (!$field['required'] && (empty($from_date) || empty($to_date))) {
$item = date_element_empty($element, $form_state);
$errors[] = t('The dates are invalid.');
}
elseif (!empty($field['todate']) && $from_date > $to_date) {
form_set_value($element[$to_field], $to_date, $form_state);
$errors[] = t('The To date must be greater than the From date.');
}
else {
// Convert input dates back to their UTC values and re-format to ISO
// or UNIX instead of the DATETIME format used in element processing.
$timezone = !empty($item[$tz_field]) ? $item[$tz_field] : $element['#date_timezone'];
$timezone_db = date_get_timezone_db($field['tz_handling']);
$item[$tz_field] = $timezone;
$from_date = date_make_date($from_date, $timezone);
$item[$offset_field] = date_offset_get($from_date);
$to_date = date_make_date($to_date, $timezone);
$test_from = date_format($from_date, 'r');
$test_to = date_format($to_date, 'r');
$item[$offset_field2] = date_offset_get($to_date);
date_timezone_set($from_date, timezone_open($timezone_db));
date_timezone_set($to_date, timezone_open($timezone_db));
$item[$from_field] = date_format($from_date, date_type_format($field['type']));
$item[$to_field] = date_format($to_date, date_type_format($field['type']));
if (isset($form_values[$field_name]['rrule'])) {
$item['rrule'] = $form_values[$field['field_name']]['rrule'];
}
// If the db timezone is not the same as the display timezone
// and we are using a date with time granularity,
// test a roundtrip back to the original timezone to catch
// invalid dates, like 2AM on the day that spring daylight savings
// time begins in the US.
$granularity = date_format_order($element[$from_field]['#date_format']);
if ($timezone != $timezone_db && date_has_time($granularity)) {
date_timezone_set($from_date, timezone_open($timezone));
date_timezone_set($to_date, timezone_open($timezone));
if ($test_from != date_format($from_date, 'r')) {
$errors[] = t('The From date is invalid.');
}
if ($test_to != date_format($to_date, 'r')) {
$errors[] = t('The To date is invalid.');
}
}
if (empty($errors)) {
form_set_value($element, $item, $form_state);
}
}
}
if (!empty($errors)) {
if ($field['multiple']) {
form_set_error($error_field, t('There are errors in @field_name value #@delta:', array('@field_name' => $field['widget']['label'], '@delta' => $delta + 1)) . theme('item_list', $errors));
}
else {
form_set_error($error_field, t('There are errors in @field_name:', array('@field_name' => $field['widget']['label'])) . theme('item_list', $errors));
}
}
}
/**
* Handle widget processing.
*/
function date_widget_validate($element, &$form_state) {
$field = $form_state['#field_info'][$element['#field_name']];
if (module_exists('date_repeat') && $field['repeat'] == 1) {
module_load_include('inc', 'date', 'date_repeat');
return _date_repeat_widget_validate($element, $form_state);
}
}
/**
* Determine the input format for this element.
*/
function date_input_format($element, $field) {
if (!empty($field['widget']['input_format_custom'])) {
return $field['widget']['input_format_custom'];
}
elseif (!empty($field['widget']['input_format']) && $field['widget']['input_format'] != 'site-wide') {
return $field['widget']['input_format'];
}
return variable_get('date_format_short', 'm/d/Y - H:i');
}
/**
* Theme from/to date combination on form.
*/
function theme_date_combo($element) {
$field = content_fields($element['#field_name'], $element['#type_name']);
if (!$field['todate']) {
return $element['#children'];
}
// Group from/to items together in fieldset.
$fieldset = array(
'#title' => check_plain($field['widget']['label']) .' '. ($element['#delta'] > 0 ? intval($element['#delta'] + 1) : ''),
'#value' => $element['#children'],
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#description' => $element['#fieldset_description'],
'#attributes' => array(),
);
return theme('fieldset', $fieldset);
}

View file

@ -0,0 +1,186 @@
<?php
/**
* @file
* An extended subclass for field handling that adds multiple field grouping.
*
* Fields that want multiple value grouping options in addition to basic
* field and formatter handling can extend this class.
*/
class date_handler_field_multiple extends content_handler_field_multiple {
function option_definition() {
$options = parent::option_definition();
$options['repeat'] = array(
'contains' => array(
'show_repeat_rule' => array('default' => ''),
)
);
$options['multiple'] = array(
'contains' => array(
'group' => array('default' => TRUE),
'multiple_number' => array('default' => ''),
'multiple_from' => array('default' => ''),
'multiple_to' => array('default' => ''),
)
);
$options['fromto'] = array(
'contains' => array(
'fromto' => array('default' => 'both'),
)
);
return $options;
}
/**
* Provide 'group multiple values' option,
* adapted to the needs of the Date module.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
unset($form['multiple']);
$field = $this->content_field;
$options = $this->options;
$form += date_formatter_settings($form_state, $field, $options, TRUE);
$form['multiple']['#weight'] = 1;
$form['multiple']['group'] = array(
'#title' => t('Group multiple values'),
'#type' => 'checkbox',
'#default_value' => $options['multiple']['group'],
'#description' => t('If unchecked, each item in the field will create a new row, which may appear to cause duplicates. This setting is not compatible with click-sorting in table displays.'),
);
}
function pre_render(&$values) {
// If there are no values to render (displaying a summary, or query returned no results),
// or if this is not a grouped field, do nothing specific.
if (isset($this->view->build_info['summary']) || empty($values) || !$this->defer_query) {
return parent::pre_render($values);
}
$field = $this->content_field;
$db_info = content_database_info($field);
$options = $this->options;
$this->view->date_info->date_handler_fields = date_handler_fields($this->view);
// Build the list of vids to retrieve.
// TODO: try fetching from cache_content first ??
$vids = array();
$this->field_values = array();
foreach ($values as $result) {
if (isset($result->{$this->field_alias})) {
$vids[] = $result->{$this->field_alias};
}
}
// List columns to retrieve.
$alias = content_views_tablename($field);
// Prefix aliases with '_' to avoid clashing with field columns names.
$query_columns = array(
'node.vid AS _vid',
"$alias.delta as _delta",
// nid is needed to generate the links for 'link to node' option.
'node.nid AS _nid',
);
// The actual field columns.
foreach ($db_info['columns'] as $column => $attributes) {
$query_columns[] = "$alias.$attributes[column] AS $column";
$query_fields[] = "$alias.$attributes[column]";
}
// Retrieve all values, we limit them in date_prepare_node(),
// a function that is used both by the handler and by the
// node theme to take advantage of formatter settings.
$where = array('1');
$query = 'SELECT '. implode(', ', $query_columns) .
' FROM {'. $db_info['table'] ."} $alias".
" LEFT JOIN {node} node ON node.vid = $alias.vid".
" WHERE node.vid IN (". implode(',', $vids) .') AND '. implode(' OR ', $where) .
" ORDER BY node.nid ASC, $alias.delta ASC";
$result = db_query($query);
while ($item = db_fetch_array($result)) {
// Clean up the $item from vid and delta. We keep nid for now.
$vid = $item['_vid'];
unset($item['_vid']);
$delta = !empty($item['_delta']) ? $item['_delta'] : 0;
$item['#delta'] = $item['_delta'];
unset($item['_delta']);
$this->field_values[$vid][$delta] = $item;
}
}
function render($values) {
// By this time $values is a pseudo node that will be passed
// to the theme. Add view information to it.
$values->date_info = !empty($this->view->date_info) ? $this->view->date_info : new stdClass();
$values->date_info->date_handler_fields = date_handler_fields($this->view);
// Add the formatter settings to the pseudo node.
$values->date_info->formatter_settings = $this->options;
$values->date_info->aliases = $this->aliases;
// If this is not a grouped field, use content_handler_field::render().
if (!$this->defer_query) {
return parent::render($values);
}
$field = $this->content_field;
$field_name = $field['field_name'];
$options = $this->options;
$vid = $values->{$this->field_alias};
if (isset($this->field_values[$vid])) {
// Build a pseudo-node from the retrieved values.
$node = drupal_clone($values);
// content_format and formatters will need a 'type'.
$node->type = $values->{$this->aliases['type']};
$node->nid = $values->{$this->aliases['nid']};
$node->vid = $values->{$this->aliases['vid']};
$items = $this->field_values[$vid];
$node->$field_name = $items;
// Some formatters need to behave differently depending on the build_mode
// (for instance: preview), so we provide one.
$node->build_mode = NODE_BUILD_NORMAL;
// Render items.
$formatter_name = $options['format'];
if ($items && ($formatter = _content_get_formatter($formatter_name, $field['type']))) {
$rendered = array();
if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {
// Single-value formatter.
foreach ($items as $item) {
$output = content_format($field, $item, $formatter_name, $node);
if (!empty($output)) {
$rendered[] = $this->render_link($output, (object) array('nid' => $this->aliases['nid']));
}
}
}
else {
// Multiple values formatter.
$output = content_format($field, $items, $formatter_name, $values);
if (!empty($output)) {
$rendered[] = $this->render_link($output, (object) array('nid' => $this->aliases['nid']));
}
}
if (count($rendered) > 1) {
// TODO: could we use generic field display ?
return theme('content_view_multiple_field', $rendered, $field, $values);
}
elseif ($rendered) {
return $rendered[0];
}
}
}
return '';
}
}

View file

@ -0,0 +1,209 @@
<?php
/**
* @file
* Implementation of Date Repeat API calculations for the CCK Date field.
*
* Consolidated here so the code isn't parsed if repeats aren't being used
* or processed, and to make it easier to maintain.
*
* The current implementation adds a repeat form to the date field so the user
* can select the repeat rules. That selection is built into an RRULE
* which is stored in the zero position of the field values. During widget
* validation, the rule is parsed to see what dates it will create,
* and multiple values are added to the field, one for each repeat date.
* That update only happens when the rule, the from date, or the to date
* change, no need to waste processing cycles for other changes to the node
* values.
*
* Lots of possible TODOs, the biggest one is figuring out the best
* way to handle dates with no UNTIL date since we can't add an infinite
* number of values to the field. For now, we require the UNTIL date.
*/
/**
* Widget processing for date repeat form element.
*
* Create the RRULE as a top-level element rather than a delta level
* element, we'll compute the repeat sequence in the widget validation
* to create the element delta values.
*/
function _date_repeat_widget(&$element, $field, $items, $delta) {
$element['rrule'] = array(
'#type' => 'date_repeat_rrule',
'#default_value' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
'#date_timezone' => $element['#date_timezone'],
'#date_format' => date_limit_format(date_input_format($element, $field), $field['granularity']),
'#date_text_parts' => (array) $field['widget']['text_parts'],
'#date_increment' => $field['widget']['increment'],
'#date_year_range' => $field['widget']['year_range'],
'#date_label_position' => $field['widget']['label_position'],
'#prev_value' => isset($items[0]['value']) ? $items[0]['value'] : '',
'#prev_value2' => isset($items[0]['value2']) ? $items[0]['value2'] : '',
'#prev_rrule' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
'#date_repeat_widget' => str_replace('_repeat', '', $field['widget']['type']),
'#date_repeat_collapsed' => $field['repeat_collapsed'],
);
return $element;
}
/**
* Validation for date repeat form element.
*
* Create multiple values from the RRULE results.
* Lots more work needed here.
*/
function _date_repeat_widget_validate($element, &$form_state) {
require_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_form.inc');
$form_values = $form_state['values'];
$field_name = $element['#field_name'];
$fields = content_fields();
$field = $fields[$field_name];
$item = $form_values[$field_name];
$values = date_repeat_merge($element['#post'][$field_name]['rrule'], $element['rrule']);
// If no start date was set, clean up the form and return.
// If no repeats are set, clean up the form and return.
if (empty($form_values[$field_name]['value']) || $values['FREQ'] == 'NONE') {
$form_values[$field_name]['rrule'] = NULL;
form_set_value($element, array($form_values[$field_name]), $form_state);
return;
}
// Require the UNTIL date for now.
// The RRULE has already been created by this point, so go back
// to the posted values to see if this was filled out.
$error_field = implode('][', $element['#parents']) .'][rrule][UNTIL][datetime][date';
if (empty($values['UNTIL']['datetime'])) {
form_set_error($error_field, t('The UNTIL value is required for repeating dates.'));
}
if (form_get_errors()) {
return;
}
// If the rule, the start date, or the end date have changed, re-calculate
// the repeating dates, wipe out the previous values, and populate the
// field with the new values.
// TODO
// Is it right to not do anything unless there are changes? Will that
// confuse anyone? Commenting that out for now...
$rrule = $form_values[$field_name]['rrule'];
if (!empty($rrule)
//&& ($rrule != $element['rrule']['#prev_rrule']
//|| $form_values[$field_name][0]['value'] != $element['rrule']['#prev_value']
//|| $form_values[$field_name][0]['value2'] != $element['rrule']['#prev_value2'])
) {
$item = $form_values[$field_name];
// Avoid undefined index problems on dates that don't have all parts.
$possible_items = array('value', 'value2', 'timezone', 'offset', 'offset2');
foreach ($possible_items as $key) {
if (empty($item[$key])) {
$item[$key] = '';
}
}
$value = date_repeat_build_dates($rrule, $values, $field, $item);
form_set_value($element, $value, $form_state);
}
else {
// If no changes are needed, move the RRULE back to the zero value
// item of the field.
form_set_value(array('#parents' => array($field_name, 0, 'rrule')), $rrule, $form_state);
form_set_value($element['rrule'], NULL, $form_state);
}
}
/**
* Helper function to build repeating dates from a $node_field.
*
* Pass in either the RRULE or the $form_values array for the RRULE,
* whichever is missing will be created when needed.
*/
function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) {
module_load_include('inc', 'date_api', 'date_api_ical');
$field_name = $field['field_name'];
if (empty($rrule)) {
$rrule = date_api_ical_build_rrule($rrule_values);
}
elseif (empty($rrule_values)) {
$rrule_values = date_ical_parse_rrule(NULL, $rrule);
}
// By the time we get here, the start and end dates have been
// adjusted back to UTC, but we want localtime dates to do
// things like '+1 Tuesday', so adjust back to localtime.
$timezone = date_get_timezone($field['tz_handling'], $item['timezone']);
$timezone_db = date_get_timezone_db($field['tz_handling']);
$start = date_make_date($item['value'], $timezone_db, $field['type'], $field['granularity']);
if ($timezone != $timezone_db) {
date_timezone_set($start, timezone_open($timezone));
}
if (!empty($item['value2']) && $item['value2'] != $item['value']) {
$end = date_make_date($item['value2'], date_get_timezone_db($field['tz_handling']), $field['type'], $field['granularity']);
date_timezone_set($end, timezone_open($timezone));
}
else {
$end = $start;
}
$duration = date_difference($start, $end);
$start_datetime = date_format($start, DATE_FORMAT_DATETIME);
if (!empty($rrule_values['UNTIL']['datetime'])) {
if (strlen($rrule_values['UNTIL']['datetime']) < 11) {
$rrule_values['UNTIL']['datetime'] .= ' 23:59:59';
}
$end = date_ical_date($rrule_values['UNTIL'], $timezone);
$end_datetime = date_format($end, DATE_FORMAT_DATETIME);
}
elseif (!empty($rrule_values['COUNT'])) {
$end_datetime = NULL;
}
else {
// No UNTIL and no COUNT?
return array();
}
// Split the RRULE into RRULE, EXDATE, and RDATE parts.
$parts = date_repeat_split_rrule($rrule);
$parsed_exceptions = (array) $parts[1];
$exceptions = array();
foreach ($parsed_exceptions as $exception) {
if (strlen($exception['datetime']) < 11) {
$exception['datetime'] .= ' 00:00:00';
}
$date = date_ical_date($exception, $timezone);
$exceptions[] = date_format($date, 'Y-m-d');
}
$parsed_rdates = (array) $parts[2];
$additions = array();
foreach ($parsed_rdates as $rdate) {
if (strlen($rdate['datetime']) < 11) {
$rdate['datetime'] .= ' 00:00:00';
}
$date = date_ical_date($rdate, $timezone);
$additions[] = date_format($date, 'Y-m-d');
}
$dates = date_repeat_calc($rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions);
$value = array();
foreach ($dates as $delta => $date) {
// date_repeat_calc always returns DATE_DATETIME dates, which is
// not necessarily $field['type'] dates.
// Convert returned dates back to db timezone before storing.
$date_start = date_make_date($date, $timezone, DATE_DATETIME, $field['granularity']);
date_timezone_set($date_start, timezone_open($timezone_db));
$date_end = drupal_clone($date_start);
date_modify($date_end, '+' . $duration . ' seconds');
$value[$delta] = array(
'value' => date_format($date_start, date_type_format($field['type'])),
'value2' => date_format($date_end, date_type_format($field['type'])),
'offset' => date_offset_get($date_start),
'offset2' => date_offset_get($date_end),
'timezone' => $timezone,
'rrule' => $rrule,
);
}
return $value;
}

View file

@ -0,0 +1,97 @@
<?php
/**
* @file
* Token module integration.
*/
function date_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens = array();
$tokens['date']['value'] = t("The raw date value.");
$tokens['date']['view'] = t("The formatted date.");
$tokens['date']['timestamp'] = t("The raw date timestamp.");
$tokens['date']['yyyy'] = t("Date year (four digit)");
$tokens['date']['yy'] = t("Date year (two digit)");
$tokens['date']['month'] = t("Date month (full word)");
$tokens['date']['mon'] = t("Date month (abbreviated)");
$tokens['date']['mm'] = t("Date month (two digit, zero padded)");
$tokens['date']['m'] = t("Date month (one or two digit)");
$tokens['date']['ww'] = t("Date week (two digit)");
$tokens['date']['date'] = t("Date date (YYYY-MM-DD)");
$tokens['date']['datetime'] = t("Date datetime (YYYY-MM-DDTHH:MM:SS)");
$tokens['date']['day'] = t("Date day (full word)");
$tokens['date']['ddd'] = t("Date day (abbreviation)");
$tokens['date']['dd'] = t("Date day (two digit, zero-padded)");
$tokens['date']['d'] = t("Date day (one or two digit)");
$tokens['date']['dS'] = t("Date day (one or two digit) with ordinal suffix (st, nd, rd or th)");
$tokens['date']['time'] = t("Time H:i");
$tokens['date']['to-????'] = t("If the field has a to-date defined, the same tokens exist in the form: [to-????], where ???? is the normal token.");
return $tokens;
}
}
function date_token_values($type, $object = NULL) {
if ($type == 'field') {
$item = $object[0];
$item['value'] = trim($item['value']);
$item_type = isset($item['date_type']) ? $item['date_type'] : (is_numeric($item['value']) ? DATE_UNIX : DATE_ISO);
$timezone = !empty($item['timezone']) ? $item['timezone'] : date_default_timezone_name();
$timezone_db = !empty($item['timezone_db']) ? $item['timezone_db'] : 'UTC';
$date = date_make_date($item['value'], $timezone_db, $item_type);
if (!empty($date) && $timezone_db != $timezone) {
date_timezone_set($date, timezone_open($timezone));
}
$tokens['value'] = $item['value'];
$tokens['view'] = $item['view'];
$tokens['timestamp'] = !empty($date) ? date_format_date($date, 'custom', 'U') : '';
$tokens['yyyy'] = !empty($date) ? date_format_date($date, 'custom', 'Y') : '';
$tokens['yy'] = !empty($date) ? date_format_date($date, 'custom', 'y') : '';
$tokens['month'] = !empty($date) ? date_format_date($date, 'custom', 'F') : '';
$tokens['mon'] = !empty($date) ? date_format_date($date, 'custom', 'M') : '';
$tokens['mm'] = !empty($date) ? date_format_date($date, 'custom', 'm') : '';
$tokens['m'] = !empty($date) ? date_format_date($date, 'custom', 'n') : '';
$tokens['ww'] = !empty($date) ? date_format_date($date, 'custom', 'W') : '';
$tokens['date'] = !empty($date) ? date_format_date($date, 'custom', DATE_FORMAT_DATE) : '';
$tokens['datetime'] = !empty($date) ? date_format_date($date, 'custom', DATE_FORMAT_ISO) : '';
$tokens['day'] = !empty($date) ? date_format_date($date, 'custom', 'l') : '';
$tokens['ddd'] = !empty($date) ? date_format_date($date, 'custom', 'D') : '';
$tokens['dd'] = !empty($date) ? date_format_date($date, 'custom', 'd') : '';
$tokens['d'] = !empty($date) ? date_format_date($date, 'custom', 'j') : '';
$tokens['dS'] = !empty($date) ? date_format_date($date, 'custom', 'jS') : '';
$tokens['time'] = !empty($date) ? date_format_date($date, 'custom', 'H:i') : '';
if (!empty($item['value2'])) {
$item['value2'] = trim($item['value2']);
$date = date_make_date($item['value2'], $timezone_db, $item_type);
if ($timezone_db != $timezone) {
date_timezone_set($date, timezone_open($timezone));
}
$tokens['to-value'] = $item['value2'];
$tokens['to-view'] = $item['view'];
$tokens['to-timestamp'] = !empty($date) ? date_format_date($date, 'custom', 'U') : '';
$tokens['to-yyyy'] = !empty($date) ? date_format_date($date, 'custom', 'Y') : '';
$tokens['to-yy'] = !empty($date) ? date_format_date($date, 'custom', 'y') : '';
$tokens['to-month'] = !empty($date) ? date_format_date($date, 'custom', 'F') : '';
$tokens['to-mon'] = !empty($date) ? date_format_date($date, 'custom', 'M') : '';
$tokens['to-mm'] = !empty($date) ? date_format_date($date, 'custom', 'm') : '';
$tokens['to-m'] = !empty($date) ? date_format_date($date, 'custom', 'n') : '';
$tokens['to-ww'] = !empty($date) ? date_format_date($date, 'custom', 'W') : '';
$tokens['to-date'] = !empty($date) ? date_format_date($date, 'custom', DATE_FORMAT_DATE) : '';
$tokens['to-datetime'] = !empty($date) ? date_format_date($date, 'custom', DATE_FORMAT_ISO) : '';
$tokens['to-day'] = !empty($date) ? date_format_date($date, 'custom', 'l') : '';
$tokens['to-ddd'] = !empty($date) ? date_format_date($date, 'custom', 'D') : '';
$tokens['to-dd'] = !empty($date) ? date_format_date($date, 'custom', 'd') : '';
$tokens['to-d'] = !empty($date) ? date_format_date($date, 'custom', 'j') : '';
$tokens['to-dS'] = !empty($date) ? date_format_date($date, 'custom', 'jS') : '';
$tokens['to-time'] = !empty($date) ? date_format_date($date, 'custom', 'H:i') : '';
}
return $tokens;
}
}

View file

@ -0,0 +1,249 @@
<?php
/**
* @file
* Administrative page callbacks for the date_api module.
*/
/**
* Allow users to configure additional date format types.
*/
function date_api_date_formats_form($form_state) {
// Add date_api.js and js settings.
date_api_add_system_javascript();
$form = array();
// Display existing format types and their formats.
date_api_date_format_form_elements($form);
$form = system_settings_form($form);
return $form;
}
/**
* Allow users to configure custom date formats.
*/
function date_api_configure_custom_date_formats() {
// Add date_api.js and js settings.
date_api_add_system_javascript();
$output = '';
// Get list of custom date formats.
$formats = date_get_formats('custom', TRUE);
if (!empty($formats)) {
$rows = array();
foreach ($formats as $format => $format_info) {
$display_text = date_format_date(date_now(), 'custom', $format);
$delete_link = l(t('remove'), 'admin/settings/date-time/formats/delete/' . $format_info['dfid']);
$row = array($display_text, $delete_link);
$rows[] = $row;
}
$output = theme('table', array(), $rows);
}
else {
$output = t('No custom formats configured. Please <a href="@link">add</a> some.', array('@link' => url('admin/settings/date-time/formats/add')));
}
return $output;
}
/**
* Allow users to add additional date formats.
*/
function date_api_add_date_formats_form($form_state) {
// Add date_api.js and js settings.
date_api_add_system_javascript();
$form['add_date_format'] = array(
'#type' => 'textfield',
'#title' => t('Format string'),
'#attributes' => array('class' => 'custom-format'),
'#description' => t('A user-defined date format. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => date_format_date(date_now(), 'custom', '-'))),
);
$form['update'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
'#weight' => 3,
'#submit' => array('date_api_date_time_settings_submit'),
);
$form['#validate'][] = 'date_api_date_time_settings_validate';
return $form;
}
/**
* Add drop down selects for date format types.
*
* @param &$form
* The form being altered.
*/
function date_api_date_format_form_elements(&$form) {
$form['date_formats'] = array(
'#type' => 'fieldset',
'#title' => t('Date formats'),
);
// Get list of all available date format types.
$format_types = date_get_format_types('', TRUE);
// Get list of all available date formats.
$all_formats = array();
$date_formats = date_get_formats('', TRUE); // Call this to rebuild the list, and to have default list.
foreach ($date_formats as $type => $format_info) {
$all_formats = array_merge($all_formats, $format_info);
}
$custom_formats = date_get_formats('custom');
foreach ($format_types as $type => $type_info) {
// If a system type, only show the available formats for that type and
// custom ones.
if ($type_info['locked'] == 1) {
$formats = date_get_formats($type);
if (empty($formats)) {
$formats = $all_formats;
}
elseif (!empty($custom_formats)) {
$formats = array_merge($formats, $custom_formats);
}
}
// If a user configured type, show all available date formats.
else {
$formats = $all_formats;
}
$choices = array();
foreach ($formats as $f => $format) {
$choices[$f] = date_format_date(date_now(), 'custom', $f);
}
$keys = array_keys($formats);
$default = variable_get('date_format_' . $type, array_shift($keys));
// Get format type info for this format type.
$type_info = date_get_format_types($type);
date_api_date_format_select_field($form, $type, $type_info, $default, $choices, 1);
}
}
function date_api_date_time_settings_validate($form, &$form_state) {
$formats = date_get_formats('custom');
if (!empty($formats) && in_array($form_state['values']['add_date_format'], array_keys($formats))) {
form_set_error('add_date_format', t('This format already exists. Please enter a unique format string.'));
}
}
function date_api_date_time_settings_submit($form, &$form_state) {
if (!empty($form_state['values']['add_date_format'])) {
$format = array();
$format['format'] = $form_state['values']['add_date_format'];
$format['type'] = 'custom';
$format['locked'] = 0;
$format['is_new'] = 1;
date_format_save($format);
}
// Unset, to prevent this getting saved as a variables.
unset($form_state['values']['add_date_format']);
drupal_set_message(t('Configuration saved.'));
}
/**
* Menu callback; present a form for deleting a date format.
*/
function date_api_delete_format_form(&$form_state, $dfid) {
$form = array();
$form['dfid'] = array(
'#type' => 'value',
'#value' => $dfid,
);
$format = date_get_format($dfid);
$output = confirm_form($form,
t('Are you sure you want to remove the format %format?', array('%format' => date_format_date(date_now(), 'custom', $format['format']))),
'admin/settings/date-time/formats/custom',
t('This action cannot be undone.'),
t('Remove'), t('Cancel'),
'confirm'
);
return $output;
}
/**
* Delete a configured date format.
*/
function date_api_delete_format_form_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$format = date_get_format($form_state['values']['dfid']);
date_format_delete($form_state['values']['dfid']);
drupal_set_message(t('Removed date format %format.', array('%format' => date_format_date(date_now(), 'custom', $format['format']))));
$form_state['redirect'] = 'admin/settings/date-time/formats/custom';
}
}
/**
* Menu callback; present a form for deleting a date format type.
*/
function date_api_delete_format_type_form(&$form_state, $format_type) {
$form = array();
$form['format_type'] = array(
'#type' => 'value',
'#value' => $format_type,
);
$type_info = date_get_format_types($format_type);
$output = confirm_form($form,
t('Are you sure you want to remove the format type %format?', array('%format' => $type_info['title'])),
'admin/settings/date-time/formats',
t('This action cannot be undone.'),
t('Remove'), t('Cancel'),
'confirm'
);
return $output;
}
/**
* Delete a configured date format type.
*/
function date_api_delete_format_type_form_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$type_info = date_get_format_types($form_state['values']['format_type']);
date_format_type_delete($form_state['values']['format_type']);
drupal_set_message(t('Removed date format type %format.', array('%format' => $type_info['title'])));
$form_state['redirect'] = 'admin/settings/date-time/formats';
}
}
/**
* Helper function; return form fields for date format selects.
*/
function date_api_date_format_select_field(&$form, $type, $type_info, $default, $choices, $show_remove = 0) {
// Show date format select list.
$form['date_formats']['date_format_' . $type] = array(
'#prefix' => '<div class="date-container"><div class="select-container">',
// Leave the date-container div open if we are going to be adding to and
// then closing it below.
'#suffix' => ($show_remove == 1 && $type_info['locked'] == 0) ? '</div>' : '</div></div>',
'#type' => 'select',
'#title' => t('!type date format', array('!type' => t($type_info['title']))),
'#attributes' => array('class' => 'date-format'),
'#default_value' => (isset($choices[$default]) ? $default : 'custom'),
'#options' => $choices,
);
// If this isn't a system provided type, allow the user to remove it from
// the system.
if ($show_remove == 1 && $type_info['locked'] == 0) {
$form['date_formats']['date_format_' . $type . '_delete'] = array(
'#prefix' => '<div class="date-format-delete">',
'#suffix' => '</div></div>',
'#value' => l(t('remove'), 'admin/settings/date-time/delete/' . $type),
);
}
}

View file

@ -0,0 +1,11 @@
name = Date API
description = A Date API that can be used by other modules.
package = Date/Time
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,508 @@
<?php
function date_api_set_variables() {
// Set absolute minimum and maximum year for dates on this site.
// There is actually no maximum and minimum year in PHP 5, but a date with
// a year less than 0 would result in negative ISO and DATETIME dates,
// like -1250-01-01T00:00:00, which probably won't make sense or work
// correctly anywhere.
//
// The odd construct of using variable_get() instead of variable_set()
// is so we don't accidentally write over an existing value. If
// no value is set, variable_get() will set it.
variable_get('date_max_year', 4000);
variable_get('date_min_year', 1);
variable_get('date_php_min_year', 1901);
// Set an API version in a way that other modules can test for compatibility.
variable_set('date_api_version', '5.2');
if (version_compare(PHP_VERSION, '5.2', '<') && !module_exists('date_php4')) {
module_enable(array('date_php4'));
}
// The timezone module was originally going to be optional
// but too many things break without it.
if (!module_exists('date_timezone')) {
module_enable(array('date_timezone'));
}
// NULL is used for the default setting of date_default_timezone_name
// to have a way to tell that no site timezone name has been implemented.
// Otherwise, many functions would use 'UTC' incorrectly and
// produce unreliable and odd results. This way functions can test for a
// value and not use this if it is empty.
//
// The odd construct of using variable_get() instead of variable_set()
// is so we don't accidentally write over an existing value. If
// no value is set, variable_get() will set it to NULL.
variable_get('date_default_timezone_name', NULL);
}
/**
* Implementation of hook_schema().
*/
function date_api_schema() {
$schema['date_format_types'] = array(
'description' => 'For storing configured date format types.',
'fields' => array(
'type' => array(
'description' => 'The date format type, e.g. medium.',
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'title' => array(
'description' => 'The human readable name of the format type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'locked' => array(
'description' => 'Whether or not this is a system provided format.',
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'not null' => TRUE,
),
),
'primary key' => array('type'),
);
$schema['date_formats'] = array(
'description' => 'For storing configured date formats.',
'fields' => array(
'dfid' => array(
'description' => 'The date format identifier.',
'type' => 'serial',
'not null' => TRUE,
'unsigned' => TRUE,
),
'format' => array(
'description' => 'The date format string.',
'type' => 'varchar',
'length' => 100,
'not null' => TRUE,
),
'type' => array(
'description' => 'The date format type, e.g. medium.',
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'locked' => array(
'description' => 'Whether or not this format can be modified.',
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'not null' => TRUE,
),
),
'primary key' => array('dfid'),
'unique keys' => array('formats' => array('format', 'type')),
);
$schema['date_format_locale'] = array(
'description' => 'For storing configured date formats for each locale.',
'fields' => array(
'format' => array(
'description' => 'The date format string.',
'type' => 'varchar',
'length' => 100,
'not null' => TRUE,
),
'type' => array(
'description' => 'The date format type, e.g. medium.',
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'language' => array(
'description' => 'A {languages}.language for this format to be used with.',
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
),
),
'primary key' => array('type', 'language'),
);
return $schema;
}
/**
* Implementation of hook_schema_alter(). We alter $schema by reference.
*
* @param $schema
* The system-wide schema collected by drupal_get_schema().
*/
function date_api_schema_alter(&$schema) {
// Add field to existing schema.
$schema['users']['fields']['timezone_name'] = array(
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
'default' => '',
'description' => t('Per-user timezone name.'),
);
}
/**
* Implementation of hook_install().
*/
function date_api_install() {
drupal_install_schema('date_api');
// date_api_set_variables can install date_timezone and date_php4. The
// date_timezone_install() function does a module_enable('date_api'). This
// means that date_api_enable() can be called before date_api_install()
// finishes! So the date_api schema needs to be installed before this line!
date_api_set_variables();
$ret = array();
db_add_field($ret, "users", "timezone_name", array('type' => 'varchar', 'length' => 50, 'not null' => TRUE, 'default' => ''));
// Make sure MYSQL does not stupidly do case-insensitive
// searches and indexes on our formats.
// @see http://pure.rednoize.com/2006/11/26/mysql-collation-matters-when-using-unique-indexes/
// @see http://jjinux.blogspot.com/2009/03/mysql-case-sensitivity-hell.html
// @see http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
global $db_type;
if ($db_type == 'mysql' || $db_type == 'mysqli') {
$sql = "ALTER TABLE {date_formats} CHANGE format format VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL";
$ret[] = update_sql($sql);
$sql = "ALTER TABLE {date_format_locale} CHANGE format format VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL";
$ret[] = update_sql($sql);
}
return $ret;
}
/**
* Implementation of hook_enable().
*/
function date_api_enable() {
// When module is enabled, build the list of date formats and types. This
// includes those provided by this module and other contrib modules. As the
// date_format tables are created but the schema hasn't been updated, force
// a refresh so we can use the schema API.
drupal_get_schema('', TRUE);
// Ensure schema has been installed - order of things gets out of sync because
// date_api_set_variables() in date_api_install() enables the 'date_timezone'
// module, which in return enables the 'date_api' module!
if (db_table_exists('date_format_types')) {
date_formats_rebuild();
}
date_api_set_variables();
}
/**
* Implementation of hook_uninstall().
*/
function date_api_uninstall() {
$ret = array();
db_drop_field($ret, "users", "timezone_name");
cache_clear_all('date_timezone_identifiers_list', 'cache');
$variables = array(
'date_api_version',
'date_min_year',
'date_max_year',
'date_php_min_year',
'date_db_tz_support',
'date_api_use_iso8601',
);
foreach ($variables as $variable) {
variable_del($variable);
}
if (db_table_exists('views_display')) {
$displays = array(
'date_nav',
);
db_query("DELETE FROM {views_display} WHERE display_plugin IN ('". implode("','", $displays) ."')");
db_query("DELETE FROM {cache_views}");
}
drupal_uninstall_schema('date_api');
return $ret;
}
/**
* Implementation of hook_requirements().
* Make sure Date PHP4 is installed if running less than PHP 5.2.
*/
function date_api_requirements($phase) {
$requirements = array();
$t = get_t();
switch ($phase) {
case 'runtime':
$tz_name = variable_get('date_default_timezone_name', NULL);
$error = FALSE;
if (version_compare(PHP_VERSION, '5.2', '<') && !module_exists('date_php4')) {
$error = TRUE;
$severity = REQUIREMENT_ERROR;
$value = $t('The Date API module requires the <a href="@link">Date PHP4 module</a> for PHP versions less than 5.2.', array('@link' => url('admin/build/modules')));
}
if ($error) {
$requirements['date_php4'] = array(
'title' => $t('Date API requirements'),
'value' => $value,
'severity' => $severity,
);
}
break;
case 'install':
break;
}
return $requirements;
}
function date_api_update_last_removed() {
return 5201;
}
/**
* Make sure all the appropriate modules get enabled.
* Repeated again just to be sure they are set.
*/
function date_api_update_6000() {
$ret = array();
// don't attempt to upgrade if views is not yet upgraded.
if (module_exists('views') && drupal_get_installed_schema_version('views', TRUE) < 6000) {
$ret = array();
drupal_set_message(t('date module cannot be updated until after Views has been updated. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
$ret['#abort'] = array('success' => FALSE, 'query' => t('date.module has updates, but cannot be updated until views.module is updated first.'));
return $ret;
}
date_api_set_variables();
return $ret;
}
/**
* Rebuild the theme registry and all the caches.
* needed to pick up changes created by updated Views API.
*/
function date_api_update_6001() {
$ret = array();
// don't attempt to upgrade if views is not yet upgraded.
if (module_exists('views') && drupal_get_installed_schema_version('views', TRUE) < 6000) {
$ret = array();
drupal_set_message(t('date module cannot be updated until after Views has been updated. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
$ret['#abort'] = array('success' => FALSE, 'query' => t('date.module has updates, but cannot be updated until views.module is updated first.'));
return $ret;
}
if (db_table_exists('cache_content')) {
db_query('DELETE FROM {cache_content}');
}
if (db_table_exists('cache_views')) {
db_query('DELETE FROM {cache_views}');
}
if (db_table_exists('views_object_cache')) {
db_query('DELETE FROM {views_object_cache}');
}
db_query("DELETE FROM {cache} where cid LIKE 'theme_registry%'");
return $ret;
}
/**
* Create new date format tables.
*/
function date_api_update_6002() {
$ret = array();
// don't attempt to upgrade if views is not yet upgraded.
if (module_exists('views') && drupal_get_installed_schema_version('views', TRUE) < 6000) {
$ret = array();
drupal_set_message(t('date module cannot be updated until after Views has been updated. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
$ret['#abort'] = array('success' => FALSE, 'query' => t('date.module has updates, but cannot be updated until views.module is updated first.'));
return $ret;
}
$schema['date_format_types'] = array(
'fields' => array(
'type' => array(
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'locked' => array(
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'not null' => TRUE,
),
),
'primary key' => array('type'),
);
$schema['date_format'] = array(
'fields' => array(
'dfid' => array(
'type' => 'serial',
'not null' => TRUE,
'unsigned' => TRUE,
),
'format' => array(
'type' => 'varchar',
'length' => 100,
'not null' => TRUE,
),
'type' => array(
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'locked' => array(
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'not null' => TRUE,
),
),
'primary key' => array('dfid'),
'unique keys' => array('format' => array('format', 'type')),
);
$schema['date_format_locale'] = array(
'fields' => array(
'format' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'type' => array(
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'language' => array(
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
),
),
'primary key' => array('type', 'language'),
);
db_create_table($ret, 'date_format_types', $schema['date_format_types']);
db_create_table($ret, 'date_format', $schema['date_format']);
db_create_table($ret, 'date_format_locale', $schema['date_format_locale']);
return $ret;
}
function date_api_update_6003() {
$ret = array();
db_change_field($ret, 'date_format_types', 'type', 'type', array('type' => 'varchar', 'length' => 200, 'not null' => TRUE));
db_change_field($ret, 'date_format', 'type', 'type', array('type' => 'varchar', 'length' => 200, 'not null' => TRUE));
db_change_field($ret, 'date_format', 'format', 'format', array('type' => 'varchar', 'length' => 100, 'not null' => TRUE));
db_change_field($ret, 'date_format_locale', 'type', 'type', array('type' => 'varchar', 'length' => 200, 'not null' => TRUE));
db_change_field($ret, 'date_format_locale', 'format', 'format', array('type' => 'varchar', 'length' => 100, 'not null' => TRUE));
db_drop_unique_key($ret, 'date_format', 'format');
db_add_unique_key($ret, 'date_format', 'format', array('format', 'type'));
return $ret;
}
/**
* The "date_format" table is missing on boxes having MySQL 5.0.67 installed.
* There seems to be a bug in MySQL that prevents the creation of tables with
* a name "date_format" and indexes with the name "format".
*
* We rename the table and index as a workaround.
*/
function date_api_update_6004() {
$ret = array();
$schema['date_formats'] = array(
'description' => 'For storing configured date formats.',
'fields' => array(
'dfid' => array(
'description' => 'The date format identifier.',
'type' => 'serial',
'not null' => TRUE,
'unsigned' => TRUE,
),
'format' => array(
'description' => 'The date format string.',
'type' => 'varchar',
'length' => 100,
'not null' => TRUE,
),
'type' => array(
'description' => 'The date format type, e.g. medium.',
'type' => 'varchar',
'length' => 200,
'not null' => TRUE,
),
'locked' => array(
'description' => 'Whether or not this format can be modified.',
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'not null' => TRUE,
),
),
'primary key' => array('dfid'),
'unique keys' => array('formats' => array('format', 'type')),
);
// Create missing table.
if (!db_table_exists('date_format')) {
db_create_table($ret, 'date_formats', $schema['date_formats']);
date_formats_rebuild();
}
// Rename existing table and index.
else {
db_drop_unique_key($ret, 'date_format', 'format');
if (db_table_exists('date_formats')) {
db_drop_table($ret, 'date_format');
}
else {
db_rename_table($ret, 'date_format', 'date_formats');
db_add_unique_key($ret, 'date_formats', 'formats', array('format', 'type'));
}
}
return $ret;
}
/**
* Make sure MYSQL does not stupidly do case-insensitive
* searches and indexes on our formats.
* @see http://pure.rednoize.com/2006/11/26/mysql-collation-matters-when-using-unique-indexes/
* @see http://jjinux.blogspot.com/2009/03/mysql-case-sensitivity-hell.html
* @see http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
*/
function date_api_update_6005() {
global $db_type;
$ret = array();
if ($db_type == 'mysql' || $db_type == 'mysqli') {
$sql = "ALTER TABLE {date_formats} CHANGE format format VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL";
$ret[] = update_sql($sql);
$sql = "ALTER TABLE {date_format_locale} CHANGE format format VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL";
$ret[] = update_sql($sql);
}
return $ret;
}
/**
* Rename the date_format_dfid_seq to date_formats_dfid_seq, as this was missed in 6004
* and causes inserts via the UI to fail on PostgreSQL.
*/
function date_api_update_6006() {
global $db_type;
$ret = array();
if ($db_type == 'pgsql' && db_table_exists('date_format_dfid_seq')) {
$sql = "ALTER TABLE {date_format}_dfid_seq RENAME TO {date_formats}_dfid_seq";
$ret[] = update_sql($sql);
}
return $ret;
}

View file

@ -0,0 +1,21 @@
/**
* Show/hide custom format sections on the date-time settings page.
*/
Drupal.behaviors.dateDateTime = function(context) {
// Show/hide custom format depending on the select's value.
$('select.date-format:not(.date-time-processed)', context).change(function() {
$(this).addClass('date-time-processed').parents("div.date-container").children("div.custom-container")[$(this).val() == "custom" ? "show" : "hide"]();
});
// Attach keyup handler to custom format inputs.
$('input.custom-format:not(.date-time-processed)', context).addClass('date-time-processed').keyup(function() {
var input = $(this);
var url = Drupal.settings.dateDateTime.lookup +(Drupal.settings.dateDateTime.lookup.match(/\?q=/) ? "&format=" : "?format=") + Drupal.encodeURIComponent(input.val());
$.getJSON(url, function(data) {
$("div.description span", input.parent()).html(data);
});
});
// Trigger the event handler to show the form input if necessary.
$('select.date-format', context).trigger('change');
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,634 @@
<?php
/**
* @file
* Date API elements themes and validation.
* This file is only included during the edit process to reduce memory usage.
*/
/**
* Implementation of hook_elements().
*
* Parameters for date form elements, designed to have sane defaults so any
* or all can be omitted.
*
* Fill the element #default_value with a date in datetime format,
* (YYYY-MM-DD HH:MM:SS), adjusted to the proper local timezone.
*
* NOTE - Converting a date stored in the database from UTC to the local zone
* and converting it back to UTC before storing it is not handled by this
* element and must be done in pre-form and post-form processing!!
*
* The date_select element will create a collection of form elements, with a
* separate select or textfield for each date part. The whole collection will
* get re-formatted back into a date value of the requested type during validation.
*
* The date_text element will create a textfield that can contain a whole
* date or any part of a date as text. The user input value will be re-formatted
* back into a date value of the requested type during validation.
*
* The date_timezone element will create a drop-down selector to pick a
* timezone name.
*
* #date_timezone
* The local timezone to be used to create this date.
*
* #date_format
* A format string that describes the format and order of date parts to
* display in the edit form for this element. This makes it possible
* to show date parts in a custom order, or to leave some of them out.
* Be sure to add 'A' or 'a' to get an am/pm selector. Defaults to the
* short site default format.
*
* #date_label_position
* Handling option for date part labels, like 'Year', 'Month', and 'Day',
* can be 'above' the date part, 'within' it, or 'none', default is 'above'.
* The 'within' option shows the label as the first option in a select list
* or the default value for an empty textfield, taking up less screen space.
*
* #date_increment
* Increment minutes and seconds by this amount, default is 1.
*
* #date_year_range
* The number of years to go back and forward in a year selector,
* default is -3:+3 (3 back and 3 forward).
*
* #date_text_parts
* Array of date parts that should use textfields instead of selects
* i.e. array('year') will format the year as a textfield and other
* date parts as drop-down selects.
*/
function _date_api_elements() {
$date_base = array(
'#input' => TRUE, '#tree' => TRUE,
'#date_timezone' => date_default_timezone_name(),
'#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'),
'#date_text_parts' => array(),
'#date_increment' => 1,
'#date_year_range' => '-3:+3',
'#date_label_position' => 'above',
);
$type['date_select'] = array_merge($date_base, array(
'#process' => array('date_select_process'),
));
$type['date_text'] = array_merge($date_base, array(
'#process' => array('date_text_process'),
));
$type['date_timezone'] = array(
'#input' => TRUE, '#tree' => TRUE,
'#process' => array('date_timezone_process'),
);
return $type;
}
/**
* Create a timezone form element.
*
* @param array $element
* @return array
* the timezone form element
*/
function date_timezone_process($element, $edit, $form_state, $form) {
$element['#tree'] = TRUE;
$element['timezone'] = array(
'#type' => 'select',
'#title' => theme('date_part_label_timezone', 'select', $element),
'#default_value' => $element['#value'],
'#options' => date_timezone_names($element['#required']),
'#weight' => $element['#weight'],
'#required' => $element['#required'],
'#theme' => 'date_select_element',
);
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_timezone_validate');
}
else {
$element['#element_validate'] = array('date_timezone_validate');
}
// TODO This sometimes causes problems, do we need it?
//$element['#attributes'] = array('class' => 'date-timezone clear-block');
return $element;
}
/**
* Text date input form.
*
* Display all or part of a date in a single textfield.
*
* The exact parts displayed in the field are those in #date_granularity.
* The display of each part comes from #date_format.
*
* In regular FAPI processing $element['#value'] will contain a string
* value before the form is submitted, and an array during submission.
*
* In regular FAPI processing $edit is empty until the form is submitted
* when it will contain an array.
*
* Views widget processing now receives the same values as normal FAPI
* processing (that was not true in Views 1).
*
*/
function date_text_process($element, $edit, $form_state, $form) {
$date = NULL;
$granularity = date_format_order($element['#date_format']);
// We sometimes get $edit without $edit['date'] from
// Views exposed filters.
if (!empty($edit) && is_array($edit) && !empty($edit['date'])) {
$datetime = date_convert_from_custom($edit['date'], $element['#date_format']);
$date = date_make_date($datetime, $element['#date_timezone'], DATE_DATETIME, $granularity);
}
elseif (!empty($element['#value'])) {
$date = date_make_date($element['#value'], $element['#date_timezone'], DATE_DATETIME, $granularity);
}
$element['#tree'] = TRUE;
$element['date']['#type'] = 'textfield';
$element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight'];
$element['date']['#default_value'] = is_object($date) ? date_format_date($date , 'custom', $element['#date_format']) : '';
$element['date']['#attributes'] = array('class' => (isset($element['#attributes']['class']) ? $element['#attributes']['class'] : '') .' date-date');
$element['date']['#description'] = ' '. t('Format: @date', array('@date' => date_format_date(date_now(), 'custom', $element['#date_format'])));
// Keep the system from creating an error message for the sub-element.
// We'll set our own message on the parent element.
//$element['date']['#required'] = $element['#required'];
$element['date']['#theme'] = 'date_textfield_element';
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_text_validate');
}
else {
$element['#element_validate'] = array('date_text_validate');
}
if (!empty($element['#force_value'])) {
$element['date']['#value'] = $element['date']['#default_value'];
}
return $element;
}
/**
* Flexible date/time drop-down selector.
*
* Splits date into a collection of date and time sub-elements, one
* for each date part. Each sub-element can be either a textfield or a
* select, based on the value of ['#date_settings']['text_fields'].
*
* The exact parts displayed in the field are those in #date_granularity.
* The display of each part comes from ['#date_settings']['format'].
*
* In regular FAPI processing $element['#value'] will contain a string
* value before the form is submitted, and an array during submission.
*
* In regular FAPI processing $edit is empty until the form is submitted
* when it will contain an array.
*
* Views widget processing now receives the same values as normal FAPI
* processing (that was not true in Views 1).
*
*/
function date_select_process($element, $edit, $form_state, $form) {
$date = NULL;
$granularity = date_format_order($element['#date_format']);
if (!empty($edit)) {
$date = date_make_date($edit, $element['#date_timezone'], DATE_ARRAY, $granularity);
}
elseif (!empty($element['#value'])) {
$date = date_make_date($element['#value'], $element['#date_timezone'], DATE_DATETIME, $granularity);
}
$element['#tree'] = TRUE;
date_increment_round($date, $element['#date_increment']);
$element += (array) date_parts_element($element, $date, $element['#date_format']);
// Store a hidden value for all date parts not in the current display.
$granularity = date_format_order($element['#date_format']);
$formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's');
foreach (date_nongranularity($granularity) as $field) {
if ($field != 'timezone') {
$element[$field] = array(
'#type' => 'value',
'#value' => 0,
);
}
}
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_select_validate');
}
else {
$element['#element_validate'] = array('date_select_validate');
}
return $element;
}
/**
* Create form elements for one or more date parts.
*
* Get the order of date elements from the provided format.
* If the format order omits any date parts in the granularity, alter the
* granularity array to match the format, then flip the $order array
* to get the position for each element. Then iterate through the
* elements and create a sub-form for each part.
*
* @param array $element
* @param object $date
* @param array $granularity
* @param string $format
* @return array
* the form array for the submitted date parts
*/
function date_parts_element($element, $date, $format) {
$granularity = date_format_order($format);
$sub_element = array('#granularity' => $granularity);
$order = array_flip($granularity);
$hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g': 'G';
$month_function = strpos($element['#date_format'], 'F') !== FALSE ? 'date_month_names' : 'date_month_names_abbr';
$count = 0;
$increment = min(intval($element['#date_increment']), 1);
foreach ($granularity as $field) {
// Allow empty value as option if date is not required
// or if empty value was provided as a starting point.
$part_required = ($element['#required'] && is_object($date)) ? TRUE : FALSE;
$part_type = in_array($field, $element['#date_text_parts']) ? 'textfield' : 'select';
$sub_element[$field] = array(
'#weight' => $order[$field],
'#required' => $element['#required'],
'#attributes' => array('class' => (isset($element['#attributes']['class']) ? $element['#attributes']['class'] : '') .' date-'. $field),
);
switch ($field) {
case 'year':
$range = date_range_years($element['#date_year_range'], $date);
$min_year = $range[0];
$max_year = $range[1];
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, 'Y') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_years($min_year, $max_year, $part_required));
}
break;
case 'month':
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, 'n') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = $month_function($part_required);
}
break;
case 'day':
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, 'j') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_days($part_required));
}
break;
case 'hour':
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, $hours_format) : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_hours($hours_format, $part_required));
}
$sub_element[$field]['#prefix'] = theme('date_part_hour_prefix', $element);
break;
case 'minute':
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, 'i') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_minutes('i', $part_required, $element['#date_increment']));
}
$sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element);
break;
case 'second':
$sub_element[$field]['#default_value'] = is_object($date) ? date_format($date, 's') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_seconds('s', $part_required, $element['#date_increment']));
}
$sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element);
break;
}
// Add handling for the date part label.
$label = theme('date_part_label_'. $field, $part_type, $element);
if (in_array($field, $element['#date_text_parts'])) {
$sub_element[$field]['#type'] = 'textfield';
$sub_element[$field]['#theme'] = 'date_textfield_element';
$sub_element[$field]['#size'] = 7;
if ($element['#date_label_position'] == 'within') {
if (is_array($sub_element[$field]['#options'])) {
$sub_element[$field]['#options'] = array(
'-'. $label => '-'. $label) + $sub_element[$field]['#options'];
}
if (empty($sub_element[$field]['#default_value'])) {
$sub_element[$field]['#default_value'] = '-'. $label;
}
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element[$field]['#title'] = $label;
}
}
else {
$sub_element[$field]['#type'] = 'select';
$sub_element[$field]['#theme'] = 'date_select_element';
if ($element['#date_label_position'] == 'within') {
$sub_element[$field]['#options'] = array(
'' => '-'. $label) + $sub_element[$field]['#options'];
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element[$field]['#title'] = $label;
}
}
// Views exposed filters are treated as submitted even if not,
// so force the #default value in that case. Make sure we set
// a default that is in the option list.
if (!empty($element['#force_value'])) {
$options = $sub_element[$field]['#options'];
$default = !empty($sub_element[$field]['#default_value']) ? $sub_element[$field]['#default_value'] : array_shift($options);
$sub_element[$field]['#value'] = $default;
}
}
if (($hours_format == 'g' || $hours_format == 'h') && date_has_time($granularity)) {
$sub_element['ampm'] = array(
'#type' => 'select',
'#default_value' => is_object($date) ? (date_format($date, 'G') >= 12 ? 'pm' : 'am') : '',
'#options' => drupal_map_assoc(date_ampm()),
'#weight' => 8,
'#attributes' => array('class' => 'date-ampm'),
);
if ($element['#date_label_position'] == 'within') {
$sub_element['ampm']['#options'] = array('' => '-'. theme('date_part_label_ampm', 'ampm', $element)) + $sub_element['ampm']['#options'];
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element['ampm']['#title'] = theme('date_part_label_ampm', 'ampm', $element);
}
}
return $sub_element;
}
/**
* Validation function for date selector.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*
*/
function date_select_validate($element, &$form_state) {
if (is_string($element['#value'])) {
return;
}
// Strip field labels out of the results.
foreach ($element['#value'] as $field => $field_value) {
if (drupal_substr($field_value, 0, 1) == '-') {
$element['#value'][$field] = '';
}
}
$error_field = implode('][', $element['#parents']);
$errors = array();
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
if (in_array('year', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['year']))) {
if ($element['#value']['year'] < variable_get('date_min_year', 1) || $element['#value']['year'] > variable_get('date_max_year', 4000)) {
$errors[] = t('The year must be a number between %min and %max.', array(
'%min' => variable_get('date_min_year', 1), '%max' => variable_get('date_max_year', 4000)));
}
else {
$year = $element['#value']['year'];
}
}
if (in_array('month', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['month']))) {
if ($element['#value']['month'] < 1 || $element['#value']['month'] > 12) {
$errors[] = t('The month must be a number between 1 and 12.');
}
else {
$month = $element['#value']['month'];
}
}
if (in_array('day', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['day']))) {
$min = 1;
$max = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31;
if ($element['#value']['day'] < $min || $element['#value']['day'] > $max) {
$errors[] = t('The day must be a number between !min and !max.', array('!min' => $min, '!max' => $max));
}
}
if (in_array('hour', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['hour']))) {
$min = isset($element['#value']['ampm']) ? 1 : 0;
$max = isset($element['#value']['ampm']) ? 12 : 23;
if ($element['#value']['hour'] < $min || $element['#value']['hour'] > $max) {
$errors[] = t('The hour must be a number between !min and !max.', array('!min' => $min, '!max' => $max));
}
}
if (in_array('minute', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['minute']))) {
$min = 0;
$max = 59;
if ($element['#value']['minute'] < $min || $element['#value']['minute'] > $max) {
$errors[] = t('The minute must be a number between !min and !max.', array('!min' => $min, '!max' => $max));
}
}
if (in_array('second', $element['#granularity']) && ($element['#required'] || !empty($element['#value']['second']))) {
$min = 0;
$max = 59;
if ($element['#value']['second'] < $min || $element['#value']['second'] > $max) {
$errors[] = t('The second must be a number between !min and !max.', array('!min' => $min, '!max' => $max));
}
}
if (isset($element['#value']['ampm'])) {
if ($element['#value']['ampm'] == 'pm' && $element['#value']['hour'] < 12) {
$element['#value']['hour'] += 12;
}
elseif ($element['#value']['ampm'] == 'am' && $element['#value']['hour'] == 12) {
$element['#value']['hour'] -= 12;
}
}
$value = date_select_input_value($element);
if (empty($value) && empty($errors) && $element['#required']) {
$errors[] = t('A valid value is required.');
}
if (!empty($errors)) {
array_unshift($errors, t('Field %field has errors.', array('%field' => $label)));
form_set_error($error_field, implode(' ', $errors));
}
// If there are no errors and the value is valid, set it.
if (empty($errors) && !empty($value)) {
form_set_value($element, $value, $form_state);
}
else {
form_set_value($element, NULL, $form_state);
}
}
/**
* Helper function for extracting a date value out of user input.
*/
function date_select_input_value($element) {
$granularity = date_format_order($element['#date_format']);
if (date_is_valid($element['#value'], DATE_ARRAY, $granularity)) {
// Use fuzzy_datetime here to be sure year-only dates
// aren't inadvertantly shifted to the wrong year by trying
// to save '2009-00-00 00:00:00'.
return date_fuzzy_datetime(date_convert($element['#value'], DATE_ARRAY, DATE_DATETIME));
}
return NULL;
}
/**
* Validation for text input.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*
*/
function date_text_validate($element, &$form_state) {
if (is_string($element['#value'])) {
return;
}
$parents = $element['#parents'];
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
$value = date_text_input_value($element);
if (empty($value) && !empty($element['#required'])) {
form_error($element, t('A valid date is required for %title.', array('%title' => $label)));
}
elseif (empty($value) && !empty($element['#value']['date'])) {
form_error($element, t('%title is invalid.', array('%title' => $label)));
}
elseif (!empty($value)) {
form_set_value($element, $value, $form_state);
}
}
/**
* Helper function for extracting a date value out of user input.
*/
function date_text_input_value($element) {
$form_values = $element['#value'];
$granularity = date_format_order($element['#date_format']);
$input = $form_values['date'];
if (!$element['#required'] && trim($input) == '') return NULL;
$value = date_limit_value(date_convert_from_custom($input, $element['#date_format']), $granularity);
// If it creates a valid date, use it.
if (date_is_valid($value, DATE_DATETIME, $granularity)) {
return $value;
}
// TODO come back and try to find a way to use strtotime to guess
// a valid value. Previous attempts to do it were too forgiving and
// invalid input was just silently converted to 'now'.
// See http://drupal.org/node/265076.
return NULL;
}
/**
* Validation for timezone input
*
* Move the timezone value from the nested field back to the original field.
*/
function date_timezone_validate($element, &$form_state) {
form_set_value($element, $element['#value']['timezone'], $form_state);
}
/**
* Convert a date input in a custom format to a standard date type
*
* Handles conversion of translated month names (i.e. turns t('Mar') or
* t('March') into 3). Also properly handles dates input in European style
* short formats, like DD/MM/YYYY. Works by parsing the format string
* to create a regex that can be used on the input value.
*
* The original code to do this was created by Yves Chedemois (yched).
*
* @param string $date
* a date value
* @param string $format
* a date format string that describes the format of the input value
* @return mixed
* input value converted to a DATE_DATETIME value
*/
function date_convert_from_custom($date, $format) {
$array = date_format_patterns();
foreach ($array as $key => $value) {
$patterns[] = "`(^|[^\\\\\\\\])". $key ."`"; // the letter with no preceding '\'
$repl1[] = '${1}(.)'; // a single character
$repl2[] = '${1}('. $value .')'; // the
}
$patterns[] = "`\\\\\\\\([". implode(array_keys($array)) ."])`";
$repl1[] = '${1}';
$repl2[] = '${1}';
$format_regexp = preg_quote($format);
// extract letters
$regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
$regex1 = str_replace('A', '(.)', $regex1);
$regex1 = str_replace('a', '(.)', $regex1);
preg_match('`^'. $regex1 .'$`', stripslashes($format), $letters);
array_shift($letters);
// extract values
$regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
$regex2 = str_replace('A', '(AM|PM)', $regex2);
$regex2 = str_replace('a', '(am|pm)', $regex2);
preg_match('`^'. $regex2 .'$`', $date, $values);
array_shift($values);
// if we did not find all the values for the patterns in the format, abort
if (count($letters) != count($values)) {
return NULL;
}
$final_date = array('hour' => 0, 'minute' => 0, 'second' => 0,
'month' => 0, 'day' => 0, 'year' => 0);
foreach ($letters as $i => $letter) {
$value = $values[$i];
switch ($letter) {
case 'd':
case 'j':
$final_date['day'] = intval($value);
break;
case 'n':
case 'm':
$final_date['month'] = intval($value);
break;
case 'F':
$array_month_long = array_flip(date_month_names());
$final_date['month'] = $array_month_long[$value];
break;
case 'M':
$array_month = array_flip(date_month_names_abbr());
$final_date['month'] = $array_month[$value];
break;
case 'Y':
case 'y':
$year = $value;
// if no century, we add the current one ("06" => "2006")
$final_date['year'] = str_pad($year, 4, drupal_substr(date("Y"), 0, 2), STR_PAD_LEFT);
break;
case 'a':
case 'A':
$ampm = strtolower($value);
break;
case 'g':
case 'h':
case 'G':
case 'H':
$final_date['hour'] = intval($value);
break;
case 'i':
$final_date['minute'] = intval($value);
break;
case 's':
$final_date['second'] = intval($value);
break;
case 'U':
return date_convert($value, DATE_UNIX, DATE_DATETIME);
break;
}
}
if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
$final_date['hour'] += 12;
}
elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
$final_date['hour'] -= 12;
}
// Don't test for valid date, we might use this to extract
// incomplete date part info from user input.
return date_convert($final_date, DATE_ARRAY, DATE_DATETIME);
}

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Initialize the list of date formats and their locales.
*/
function _date_api_date_formats_list() {
$formats = array();
// Short date formats.
$formats[] = array(
'type' => 'short',
'format' => 'Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd.m.Y - H:i',
'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - g:ia',
'locales' => array(),
);
// Medium date formats.
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j. F Y - G:i',
'locales' => array(),
);
// Long date formats.
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j. F Y - G:i',
'locales' => array(),
);
return $formats;
}

View file

@ -0,0 +1,802 @@
<?php
/**
* @file
* Parse iCal data.
*
* This file must be included when these functions are needed.
*/
/**
* Return an array of iCalendar information from an iCalendar file.
*
* No timezone adjustment is performed in the import since the timezone
* conversion needed will vary depending on whether the value is being
* imported into the database (when it needs to be converted to UTC), is being
* viewed on a site that has user-configurable timezones (when it needs to be
* converted to the user's timezone), if it needs to be converted to the
* site timezone, or if it is a date without a timezone which should not have
* any timezone conversion applied.
*
* Properties that have dates and times are converted to sub-arrays like:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event
* 'tz' => the timezone of the date, could be blank for absolute
* times that should get no timezone conversion.
*
* Exception dates can have muliple values and are returned as arrays
* like the above for each exception date.
*
* Most other properties are returned as PROPERTY => VALUE.
*
* Each item in the VCALENDAR will return an array like:
* [0] => Array (
* [TYPE] => VEVENT
* [UID] => 104
* [SUMMARY] => An example event
* [URL] => http://example.com/node/1
* [DTSTART] => Array (
* [datetime] => 1997-09-07 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [DTEND] => Array (
* [datetime] => 1997-09-07 11:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* )
*
* @todo
* figure out how to handle this if subgroups are nested,
* like a VALARM nested inside a VEVENT.
*
* @param string $filename
* Location (local or remote) of a valid iCalendar file.
*
* @return array
* An array with all the elements from the ical.
*/
function date_ical_import($filename) {
// Fetch the iCal data. If file is a URL, use drupal_http_request. fopen
// isn't always configured to allow network connections.
if (substr($filename, 0, 4) == 'http') {
// Fetch the ical data from the specified network location.
$icaldatafetch = drupal_http_request($filename);
// Check the return result.
if ($icaldatafetch->error) {
watchdog('date ical', 'HTTP Request Error importing %filename: @error', array('%filename' => $filename, '@error' => $icaldatafetch->error));
return FALSE;
}
// Break the return result into one array entry per lines.
$icaldatafolded = explode("\n", $icaldatafetch->data);
}
else {
$icaldatafolded = @file($filename, FILE_IGNORE_NEW_LINES);
if ($icaldatafolded === FALSE) {
watchdog('date ical', 'Failed to open file: %filename', array('%filename' => $filename));
return FALSE;
}
}
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file: %filename', array('%filename' => $filename));
return FALSE;
}
return date_ical_parse($icaldatafolded);
}
/**
* Returns an array of iCalendar information from an iCalendar file.
*
* As date_ical_import() but different param.
*
* @param array $icaldatafolded
* An array of lines from an ical feed.
*
* @return array
* An array with all the elements from the ical.
*/
function date_ical_parse($icaldatafolded = array()) {
$items = array();
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file.');
return FALSE;
}
// "Unfold" wrapped lines.
$icaldata = array();
foreach ($icaldatafolded as $line) {
$out = array();
// See if this looks like the beginning of a new property or value. If not,
// it is a continuation of the previous line. The regex is to ensure that
// wrapped QUOTED-PRINTABLE data is kept intact.
if (!preg_match('/([A-Z]+)[:;](.*)/', $line, $out)) {
// Trim up to 1 leading space from wrapped line per iCalendar standard.
$line = array_pop($icaldata) . (ltrim(substr($line, 0, 1)) . substr($line, 1));
}
$icaldata[] = $line;
}
unset($icaldatafolded);
// Parse the iCal information.
$parents = array();
$subgroups = array();
$vcal = '';
foreach ($icaldata as $line) {
$line = trim($line);
$vcal .= $line . "\n";
// Deal with begin/end tags separately.
if (preg_match('/(BEGIN|END):V(\S+)/', $line, $matches)) {
$closure = $matches[1];
$type = 'V' . $matches[2];
if ($closure == 'BEGIN') {
array_push($parents, $type);
array_push($subgroups, array());
}
elseif ($closure == 'END') {
end($subgroups);
$subgroup = &$subgroups[key($subgroups)];
switch ($type) {
case 'VCALENDAR':
if (prev($subgroups) == FALSE) {
$items[] = array_pop($subgroups);
}
else {
$parent[array_pop($parents)][] = array_pop($subgroups);
}
break;
// Add the timezones in with their index their TZID.
case 'VTIMEZONE':
$subgroup = end($subgroups);
$id = $subgroup['TZID'];
unset($subgroup['TZID']);
// Append this subgroup onto the one above it.
prev($subgroups);
$parent = &$subgroups[key($subgroups)];
$parent[$type][$id] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
// Do some fun stuff with durations and all_day events and then append
// to parent.
case 'VEVENT':
case 'VALARM':
case 'VTODO':
case 'VJOURNAL':
case 'VVENUE':
case 'VFREEBUSY':
default:
// Can't be sure whether DTSTART is before or after DURATION, so
// parse DURATION at the end.
if (isset($subgroup['DURATION'])) {
date_ical_parse_duration($subgroup, 'DURATION');
}
// Add a top-level indication for the 'All day' condition. Leave it
// in the individual date components, too, so it is always available
// even when you are working with only a portion of the VEVENT
// array, like in Feed API parsers.
$subgroup['all_day'] = FALSE;
// iCal spec states 'The "DTEND" property for a "VEVENT" calendar
// component specifies the non-inclusive end of the event'. Adjust
// multi-day events to remove the extra day because the Date code
// assumes the end date is inclusive.
if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) {
// Make the end date one day earlier.
$date = date_make_date($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']);
date_modify($date, '-1 day');
$subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d');
}
// If a start datetime is defined AND there is no definition for
// the end datetime THEN make the end datetime equal the start
// datetime and if it is an all day event define the entire event
// as a single all day event.
if (!empty($subgroup['DTSTART']) &&
(empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT']))) {
$subgroup['DTEND'] = $subgroup['DTSTART'];
}
// Add this element to the parent as an array under the component
// name.
if (!empty($subgroup['DTSTART']['all_day'])) {
$subgroup['all_day'] = TRUE;
}
// Add this element to the parent as an array under the
prev($subgroups);
$parent = &$subgroups[key($subgroups)];
$parent[$type][] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
}
}
}
// Handle all other possibilities.
else {
// Grab current subgroup.
end($subgroups);
$subgroup = &$subgroups[key($subgroups)];
// Split up the line into nice pieces for PROPERTYNAME,
// PROPERTYATTRIBUTES, and PROPERTYVALUE.
preg_match('/([^;:]+)(?:;([^:]*))?:(.+)/', $line, $matches);
$name = !empty($matches[1]) ? strtoupper(trim($matches[1])) : '';
$field = !empty($matches[2]) ? $matches[2] : '';
$data = !empty($matches[3]) ? $matches[3] : '';
$parse_result = '';
switch ($name) {
// Keep blank lines out of the results.
case '':
break;
// Lots of properties have date values that must be parsed out.
case 'CREATED':
case 'LAST-MODIFIED':
case 'DTSTART':
case 'DTEND':
case 'DTSTAMP':
case 'FREEBUSY':
case 'DUE':
case 'COMPLETED':
$parse_result = date_ical_parse_date($field, $data);
break;
case 'EXDATE':
case 'RDATE':
$parse_result = date_ical_parse_exceptions($field, $data);
break;
case 'TRIGGER':
// A TRIGGER can either be a date or in the form -PT1H.
if (!empty($field)) {
$parse_result = date_ical_parse_date($field, $data);
}
else {
$parse_result = array('DATA' => $data);
}
break;
case 'DURATION':
// Can't be sure whether DTSTART is before or after DURATION in
// the VEVENT, so store the data and parse it at the end.
$parse_result = array('DATA' => $data);
break;
case 'RRULE':
case 'EXRULE':
$parse_result = date_ical_parse_rrule($field, $data);
break;
case 'STATUS':
case 'SUMMARY':
case 'DESCRIPTION':
$parse_result = date_ical_parse_text($field, $data);
break;
case 'LOCATION':
$parse_result = date_ical_parse_location($field, $data);
break;
// For all other properties, just store the property and the value.
// This can be expanded on in the future if other properties should
// be given special treatment.
default:
$parse_result = $data;
break;
}
// Store the result of our parsing.
$subgroup[$name] = $parse_result;
}
}
return $items;
}
/**
* Parses a ical date element.
*
* Possible formats to parse include:
* PROPERTY:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE-TIME:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;TZID=XXXXXXXX;VALUE=DATE:YYYYMMDD[T][HH][MM][SS]
* PROPERTY;TZID=XXXXXXXX:YYYYMMDD[T][HH][MM][SS]
*
* The property and the colon before the date are removed in the import
* process above and we are left with $field and $data.
*
* @param string $field
* The text before the colon and the date, i.e.
* ';VALUE=DATE:', ';VALUE=DATE-TIME:', ';TZID='
* @param string $data
* The date itself, after the colon, in the format YYYYMMDD[T][HH][MM][SS][Z]
* 'Z', if supplied, means the date is in UTC.
*
* @return array
* $items array, consisting of:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event with no time
* 'tz' => the timezone of the date, could be blank if the ical
* has no timezone; the ical specs say no timezone
* conversion should be done if no timezone info is
* supplied
* @todo
* Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The
* period may include a duration, or a date and a duration, or two dates, so
* would have to be split into parts and run through date_ical_parse_date()
* and date_ical_parse_duration(). This is not commonly used, so ignored for
* now. It will take more work to figure how to support that.
*/
function date_ical_parse_date($field, $data) {
$items = array('datetime' => '', 'all_day' => '', 'tz' => '');
if (empty($data)) {
return $items;
}
// Make this a little more whitespace independent.
$data = trim($data);
// Turn the properties into a nice indexed array of
// array(PROPERTYNAME => PROPERTYVALUE);
$field_parts = preg_split('/[;:]/', $field);
$properties = array();
foreach ($field_parts as $part) {
if (strpos($part, '=') !== FALSE) {
$tmp = explode('=', $part);
$properties[$tmp[0]] = $tmp[1];
}
}
// Make this a little more whitespace independent.
$data = trim($data);
// Record if a time has been found.
$has_time = FALSE;
// If a format is specified, parse it according to that format.
if (isset($properties['VALUE'])) {
switch ($properties['VALUE']) {
case 'DATE':
preg_match(DATE_REGEX_ICAL_DATE, $data, $regs);
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
break;
case 'DATE-TIME':
preg_match(DATE_REGEX_ICAL_DATETIME, $data, $regs);
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
// Time.
$datetime .= ' ' . date_pad($regs[4]) . ':' . date_pad($regs[5]) . ':' . date_pad($regs[6]);
$has_time = TRUE;
break;
}
}
// If no format is specified, attempt a loose match.
else {
preg_match(DATE_REGEX_LOOSE, $data, $regs);
if (!empty($regs) && count($regs) > 2) {
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
if (isset($regs[4])) {
$has_time = TRUE;
// Time.
$datetime .= ' ' . (!empty($regs[5]) ? date_pad($regs[5]) : '00') .
':' . (!empty($regs[6]) ? date_pad($regs[6]) : '00') .
':' . (!empty($regs[7]) ? date_pad($regs[7]) : '00');
}
}
}
// Use timezone if explicitly declared.
if (isset($properties['TZID'])) {
$tz = $properties['TZID'];
// Fix alternatives like US-Eastern which should be US/Eastern.
$tz = str_replace('-', '/', $tz);
// Unset invalid timezone names.
module_load_include('install', 'date_timezone');
$tz = _date_timezone_replacement($tz);
if (!date_timezone_is_valid($tz)) {
$tz = '';
}
}
// If declared as UTC with terminating 'Z', use that timezone.
elseif (strpos($data, 'Z') !== FALSE) {
$tz = 'UTC';
}
// Otherwise this date is floating.
else {
$tz = '';
}
$items['datetime'] = $datetime;
$items['all_day'] = $has_time ? FALSE : TRUE;
$items['tz'] = $tz;
return $items;
}
/**
* Parse an ical repeat rule.
*
* @return array
* Array in the form of PROPERTY => array(VALUES)
* PROPERTIES include FREQ, INTERVAL, COUNT, BYDAY, BYMONTH, BYYEAR, UNTIL
*/
function date_ical_parse_rrule($field, $data) {
$data = preg_replace("/RRULE.*:/", '', $data);
$items = array('DATA' => $data);
$rrule = explode(';', $data);
foreach ($rrule as $key => $value) {
$param = explode('=', $value);
// Must be some kind of invalid data.
if (count($param) != 2) {
continue;
}
if ($param[0] == 'UNTIL') {
$values = date_ical_parse_date('', $param[1]);
}
else {
$values = explode(',', $param[1]);
}
// Treat items differently if they have multiple or single values.
if (in_array($param[0], array('FREQ', 'INTERVAL', 'COUNT', 'WKST'))) {
$items[$param[0]] = $param[1];
}
else {
$items[$param[0]] = $values;
}
}
return $items;
}
/**
* Parse exception dates (can be multiple values).
*
* @return array
* an array of date value arrays.
*/
function date_ical_parse_exceptions($field, $data) {
$data = str_replace($field . ':', '', $data);
$items = array('DATA' => $data);
$ex_dates = explode(',', $data);
foreach ($ex_dates as $ex_date) {
$items[] = date_ical_parse_date('', $ex_date);
}
return $items;
}
/**
* Parses the duration of the event.
*
* Example:
* DURATION:PT1H30M
* DURATION:P1Y2M
*
* @param array $subgroup
* Array of other values in the vevent so we can check for DTSTART.
*/
function date_ical_parse_duration(&$subgroup, $field = 'DURATION') {
$items = $subgroup[$field];
$data = $items['DATA'];
preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $data, $duration);
$items['year'] = isset($duration[1]) ? str_replace('Y', '', $duration[1]) : '';
$items['month'] = isset($duration[2]) ?str_replace('M', '', $duration[2]) : '';
$items['week'] = isset($duration[3]) ?str_replace('W', '', $duration[3]) : '';
$items['day'] = isset($duration[4]) ?str_replace('D', '', $duration[4]) : '';
$items['hour'] = isset($duration[6]) ?str_replace('H', '', $duration[6]) : '';
$items['minute'] = isset($duration[7]) ?str_replace('M', '', $duration[7]) : '';
$items['second'] = isset($duration[8]) ?str_replace('S', '', $duration[8]) : '';
$start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(date_now(), DATE_FORMAT_ISO);
$timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : variable_get('date_default_timezone_name', NULL);
if (empty($timezone)) {
$timezone = 'UTC';
}
$date = date_make_date($start_date, $timezone);
$date2 = drupal_clone($date);
foreach ($items as $item => $count) {
if ($count > 0) {
date_modify($date2, '+' . $count . ' ' . $item);
}
}
$format = isset($subgroup['DTSTART']['type']) && $subgroup['DTSTART']['type'] == 'DATE' ? 'Y-m-d' : 'Y-m-d H:i:s';
$subgroup['DTEND'] = array(
'datetime' => date_format($date2, DATE_FORMAT_DATETIME),
'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0,
'tz' => $timezone,
);
$duration = date_format($date2, 'U') - date_format($date, 'U');
$subgroup['DURATION'] = array('DATA' => $data, 'DURATION' => $duration);
}
/**
* Parse and clean up ical text elements.
*/
function date_ical_parse_text($field, $data) {
if (strstr($field, 'QUOTED-PRINTABLE')) {
$data = quoted_printable_decode($data);
}
// Strip line breaks within element.
$data = str_replace(array("\r\n ", "\n ", "\r "), '', $data);
// Put in line breaks where encoded.
$data = str_replace(array("\\n", "\\N"), "\n", $data);
// Remove other escaping.
$data = stripslashes($data);
return $data;
}
/**
* Parse location elements.
*
* Catch situations like the upcoming.org feed that uses
* LOCATION;VENUE-UID="http://upcoming.yahoo.com/venue/104/":111 First Street...
* or more normal LOCATION;UID=123:111 First Street...
* Upcoming feed would have been improperly broken on the ':' in http://
* so we paste the $field and $data back together first.
*
* Use non-greedy check for ':' in case there are more of them in the address.
*/
function date_ical_parse_location($field, $data) {
if (preg_match('/UID=[?"](.+)[?"][*?:](.+)/', $field . ':' . $data, $matches)) {
$location = array();
$location['UID'] = $matches[1];
$location['DESCRIPTION'] = stripslashes($matches[2]);
return $location;
}
else {
// Remove other escaping.
$location = stripslashes($data);
return $location;
}
}
/**
* Return a date object for the ical date, adjusted to its local timezone.
*
* @param array $ical_date
* An array of ical date information created in the ical import.
* @param string $to_tz
* The timezone to convert the date's value to.
*
* @return object
* A timezone-adjusted date object.
*/
function date_ical_date($ical_date, $to_tz = FALSE) {
// If the ical date has no timezone, must assume it is stateless
// so treat it as a local date.
if (empty($ical_date['datetime'])) {
return NULL;
}
elseif (empty($ical_date['tz'])) {
$from_tz = date_default_timezone_name();
}
else {
$from_tz = $ical_date['tz'];
}
if (strlen($ical_date['datetime']) < 11) {
$ical_date['datetime'] .= ' 00:00:00';
}
$date = date_make_date($ical_date['datetime'], $from_tz);
if ($to_tz && $ical_date['tz'] != '' && $to_tz != $ical_date['tz']) {
date_timezone_set($date, timezone_open($to_tz));
}
return $date;
}
/**
* Escape #text elements for safe iCal use.
*
* @param string $text
* Text to escape
*
* @return string
* Escaped text
*
*/
function date_ical_escape_text($text) {
$text = drupal_html_to_text($text);
$text = trim($text);
// TODO Per #38130 the iCal specs don't want : and " escaped
// but there was some reason for adding this in. Need to watch
// this and see if anything breaks.
// $text = str_replace('"', '\"', $text);
// $text = str_replace(":", "\:", $text);
$text = preg_replace("/\\\b/", "\\\\", $text);
$text = str_replace(",", "\,", $text);
$text = str_replace(";", "\;", $text);
$text = str_replace("\n", "\\n\r\n ", $text);
return trim($text);
}
/**
* Build an iCal RULE from $form_values.
*
* @param array $form_values
* An array constructed like the one created by date_ical_parse_rrule().
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* [UNTIL] => Array (
* [datetime] => 1997-21-31 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
*/
function date_api_ical_build_rrule($form_values) {
$RRULE = '';
if (empty($form_values) || !is_array($form_values)) {
return $RRULE;
}
// Grab the RRULE data and put them into iCal RRULE format.
$RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']);
$RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']);
// Unset the empty 'All' values.
if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) {
unset($form_values['BYDAY']['']);
}
if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) {
unset($form_values['BYMONTH']['']);
}
if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) {
unset($form_values['BYMONTHDAY']['']);
}
if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $BYDAY = implode(",", $form_values['BYDAY'])) {
$RRULE .= ';BYDAY=' . $BYDAY;
}
if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $BYMONTH = implode(",", $form_values['BYMONTH'])) {
$RRULE .= ';BYMONTH=' . $BYMONTH;
}
if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $BYMONTHDAY = implode(",", $form_values['BYMONTHDAY'])) {
$RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY;
}
// The UNTIL date is supposed to always be expressed in UTC.
// The input date values may already have been converted to a date object on a
// previous pass, so check for that.
if (array_key_exists('UNTIL', $form_values) && array_key_exists('datetime', $form_values['UNTIL']) && !empty($form_values['UNTIL']['datetime'])) {
// We only collect a date for UNTIL, but we need it to be inclusive, so
// force it to a full datetime element at the last second of the day.
if (!is_object($form_values['UNTIL']['datetime'])) {
if (strlen($form_values['UNTIL']['datetime']) < 11) {
$form_values['UNTIL']['datetime'] .= ' 23:59:59';
$form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second')));
$form_values['UNTIL']['all_day'] = FALSE;
}
$until = date_ical_date($form_values['UNTIL'], 'UTC');
}
else {
$until = $form_values['UNTIL']['datetime'];
}
$RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z';
}
// Our form doesn't allow a value for COUNT, but it may be needed by
// modules using the API, so add it to the rule.
if (array_key_exists('COUNT', $form_values)) {
$RRULE .= ';COUNT=' . $form_values['COUNT'];
}
// iCal rules presume the week starts on Monday unless otherwise specified,
// so we'll specify it.
if (array_key_exists('WKST', $form_values)) {
$RRULE .= ';WKST=' . $form_values['WKST'];
}
else {
$RRULE .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0));
}
// Exceptions dates go last, on their own line.
// The input date values may already have been converted to a date
// object on a previous pass, so check for that.
if (isset($form_values['EXDATE']) && is_array($form_values['EXDATE'])) {
$ex_dates = array();
foreach ($form_values['EXDATE'] as $value) {
if (!empty($value['datetime'])) {
$date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($form_values['EXDATE'])) {
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE'];
}
// Exceptions dates go last, on their own line.
if (isset($form_values['RDATE']) && is_array($form_values['RDATE'])) {
$ex_dates = array();
foreach ($form_values['RDATE'] as $value) {
$date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($form_values['RDATE'])) {
$RRULE .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE'];
}
return $RRULE;
}

View file

@ -0,0 +1,881 @@
<?php
/**
* @file
* SQL date functions.
*/
/**
* A helper function to do cross-database concatation of date parts
*
* @param $array - an array of values to be concatonated in sql
* @return - correct sql string for database type
*/
function date_sql_concat($array) {
global $db_type;
switch ($db_type) {
case ('mysql'):
case ('mysqli'):
return "CONCAT(". implode(",", $array) .")";
case ('pgsql'):
return implode(" || ", $array);
}
}
/**
* Helper function to do cross-database NULL replacements
*
* @param an array of values to test for NULL values
* @return SQL statement to return the first non-NULL value in the list.
*/
function date_sql_coalesce($array) {
global $db_type;
switch ($db_type) {
case ('mysql'):
case ('mysqli'):
case ('pgsql'):
return "COALESCE(". implode(',', $array) .")";
}
}
/**
* A helper function to do cross-database padding of date parts
*
* @param $str - a string to apply padding to
* @param $size - the size the final string should be
* @param $pad - the value to pad the string with
* @param $side - the side of the string to pad
*/
function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') {
switch ($side) {
case ('r'):
return "RPAD($str, $size, '$pad')";
default:
return "LPAD($str, $size, '$pad')";
}
}
/**
* A class to manipulate date SQL.
*/
class date_sql_handler {
var $db_type = 'mysql';
var $date_type = DATE_DATETIME;
var $db_timezone = 'UTC'; // A string timezone name.
var $local_timezone = NULL; // A string timezone name.
var $db_timezone_field = NULL; // Use if the db timezone is stored in a field.
var $local_timezone_field = NULL; // Use if the local timezone is stored in a field.
var $offset_field = NULL; // Use if the offset is stored in a field.
function construct($date_type = DATE_DATETIME, $local_timezone = NULL) {
$this->db_type = $GLOBALS['db_type'];
$this->date_type = $date_type;
$this->db_timezone = 'UTC';
$this->local_timezone = isset($local_timezone) ? $local_timezone : date_default_timezone_name();
if (isset($this->definition['content_field'])) {
$this->date_handler->date_type = $this->definition['content_field']['type'];
}
date_api_set_db_timezone();
}
/**
* See if the db has timezone name support.
*/
function db_tz_support($reset = FALSE) {
$has_support = variable_get('date_db_tz_support', -1);
if ($has_support == -1 || $reset) {
date_api_set_db_timezone();
$has_support = FALSE;
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
if (version_compare(db_version(), '4.1.3', '>=')) {
$test = db_result(db_query("SELECT CONVERT_TZ('2008-02-15 12:00:00', 'UTC', 'US/Central')"));
if ($test == '2008-02-15 06:00:00') {
$has_support = TRUE;
}
}
break;
case 'pgsql':
$test = db_result(db_query("SELECT '2008-02-15 12:00:00 UTC' AT TIME ZONE 'US/Central'"));
if ($test == '2008-02-15 06:00:00') {
$has_support = TRUE;
}
break;
}
variable_set('date_db_tz_support', $has_support);
}
return $has_support;
}
/**
* Set the database timzone offset.
*
* Setting the db timezone to UTC is done to ensure consistency in date
* handling whether or not the database can do proper timezone conversion.
*
* Views filters that not exposed are cached and won't set the timezone
* so views date filters should add 'cacheable' => 'no' to their
* definitions to ensure that the database timezone gets set properly
* when the query is executed.
*
* @param $offset
* An offset value to set the database timezone to. This will only
* set a fixed offset, not a timezone, so any value other than
* '+00:00' should be used with caution.
*/
function set_db_timezone($offset = '+00:00') {
static $already_set = FALSE;
$type = $GLOBALS['db_type'];
if (!$already_set) {
if (($type == 'mysqli' || $type == 'mysql') && version_compare(db_version(), '4.1.3', '>=')) {
db_query("SET @@session.time_zone = '$offset'");
}
elseif ($type == 'pgsql') {
db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
}
$already_set = TRUE;
}
}
/**
* Return timezone offset for the date being processed.
*/
function get_offset() {
if (!empty($this->db_timezone) && !empty($this->local_timezone)) {
if ($this->db_timezone != $this->local_timezone) {
$date = date_now($this->db_timezone);
date_timezone_set($date, timezone_open($this->local_timezone));
return date_offset_get($date);
}
}
return 0;
}
/**
* Helper function to create cross-database SQL dates.
*
* @param $field
* The real table and field name, like 'tablename.fieldname'.
* @param $offset
* The name of a field that holds the timezone offset or an
* offset value. If NULL, the normal Drupal timezone handling
* will be used, if $offset = 0 no adjustment will be made.
* @return
* An appropriate SQL string for the db type and field type.
*/
function sql_field($field, $offset = NULL) {
if (drupal_strtoupper($field) == 'NOW') {
// NOW() will be in UTC since that is what we set the db timezone to.
$this->local_timezone = 'UTC';
return $this->sql_offset('NOW()', $offset);
}
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
switch ($this->date_type) {
case DATE_UNIX:
$field = "FROM_UNIXTIME($field)";
break;
case DATE_ISO:
if (version_compare(db_version(), '4.1.1', '>=')) {
$field = "STR_TO_DATE($field, '%Y-%m-%%dT%T')";
}
else {
$field = "REPLACE($field, 'T', ' ')";
}
break;
case DATE_DATETIME:
break;
}
break;
case 'pgsql':
switch ($this->date_type) {
case DATE_UNIX:
$field = "$field::ABSTIME";
break;
case DATE_ISO:
$field = "TO_DATE($field, 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS')";
break;
case DATE_DATETIME:
break;
}
break;
}
// Adjust the resulting value to the right timezone/offset.
return $this->sql_tz($field, $offset);
}
/**
* Adjust a field value by an offset in seconds.
*/
function sql_offset($field, $offset = NULL) {
if (!empty($offset)) {
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
if (version_compare(db_version(), '4.1.1', '>=')) {
return "ADDTIME($field, SEC_TO_TIME($offset))";
}
else {
return "DATE_ADD(CAST($field AS DATETIME), INTERVAL $offset SECOND)";
}
case 'pgsql':
return "($field + INTERVAL '$offset SECONDS')";;
}
}
return $field;
}
/**
* Adjust a field value by time interval.
*
* @param $field
* The field to be adjusted.
* @param $direction
* Either ADD or SUB.
* @param $count
* The number of values to adjust.
* @param $granularity
* The granularity of the adjustment, should be singular,
* like SECOND, MINUTE, DAY, HOUR.
*/
function sql_date_math($field, $direction, $count, $granularity) {
$granularity = drupal_strtoupper($granularity);
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
switch ($direction) {
case 'ADD':
return "DATE_ADD(CAST($field AS DATETIME), INTERVAL $count $granularity)";
case 'SUB':
return "DATE_SUB(CAST($field AS DATETIME), INTERVAL $count $granularity)";
}
case 'pgsql':
$granularity .= 'S';
switch ($direction) {
case 'ADD':
return "($field + INTERVAL '$count $granularity')";
case 'SUB':
return "($field - INTERVAL '$count $granularity')";
}
}
return $field;
}
/**
* Select a date value from the database, adjusting the value
* for the timezone.
*
* Check whether database timezone conversion is supported in
* this system and use it if possible, otherwise use an
* offset.
*
* @param $offset
* Set a fixed offset or offset field to use for the date.
* If set, no timezone conversion will be done and the
* offset will be used.
*/
function sql_tz($field, $offset = NULL) {
// If the timezones are values they need to be quoted, but
// if they are field names they do not.
$db_zone = $this->db_timezone_field ? $this->db_timezone_field : "'{$this->db_timezone}'";
$localzone = $this->local_timezone_field ? $this->local_timezone_field : "'{$this->local_timezone}'";
// If a fixed offset is required, use it.
if ($offset !== NULL) {
return $this->sql_offset($field, $offset);
}
// If the db and local timezones are the same, make no adjustment.
elseif ($db_zone == $localzone) {
return $this->sql_offset($field, 0);
}
// If the db has no timezone support, adjust by the offset,
// could be either a field name or a value.
elseif (!$this->db_tz_support()) {
if (!empty($this->offset_field)) {
return $this->sql_offset($field, $this->offset_field);
}
else {
return $this->sql_offset($field, $this->get_offset());
}
}
// Otherwise make a database timezone adjustment to the field.
else {
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
return "CONVERT_TZ($field, $db_zone, $localzone)";
case 'pgsql':
// WITH TIME ZONE assumes the date is using the system
// timezone, which should have been set to UTC.
return "$field::timestamp with time zone AT TIME ZONE $localzone";
}
}
}
/**
* Helper function to create cross-database SQL date formatting.
*
* @param $format
* A format string for the result, like 'Y-m-d H:i:s'.
* @param $field
* The real table and field name, like 'tablename.fieldname'.
* @return
* An appropriate SQL string for the db type and field type.
*/
function sql_format($format, $field) {
switch ($this->db_type) {
case 'mysql':
case 'mysqli':
$replace = array(
'Y' => '%Y', 'y' => '%y',
'm' => '%m', 'n' => '%c',
'd' => '%%d', 'j' => '%e',
'H' => '%H',
'i' => '%i',
's' => '%%s',
'\WW' => 'W%U',
);
$format = strtr($format, $replace);
return "DATE_FORMAT($field, '$format')";
case 'pgsql':
$replace = array(
'Y' => 'YYYY', 'y' => 'Y',
'm' => 'MM', 'n' => 'M',
'd' => 'DD', 'j' => 'D',
'H' => 'HH24',
'i' => 'MI',
's' => 'SS',
'\T' => '"T"',
//'\W' => // TODO, what should this be?
);
$format = strtr($format, $replace);
return "TO_CHAR($field, '$format')";
}
}
/**
* Helper function to create cross-database SQL date extraction.
*
* @param $extract_type
* The type of value to extract from the date, like 'MONTH'.
* @param $field
* The real table and field name, like 'tablename.fieldname'.
* @return
* An appropriate SQL string for the db type and field type.
*/
function sql_extract($extract_type, $field) {
// Note there is no space after FROM to avoid db_rewrite problems
// see http://drupal.org/node/79904.
switch (drupal_strtoupper($extract_type)) {
case ('DATE'):
return $field;
case ('YEAR'):
return "EXTRACT(YEAR FROM($field))";
case ('MONTH'):
return "EXTRACT(MONTH FROM($field))";
case ('DAY'):
return "EXTRACT(DAY FROM($field))";
case ('HOUR'):
return "EXTRACT(HOUR FROM($field))";
case ('MINUTE'):
return "EXTRACT(MINUTE FROM($field))";
case ('SECOND'):
return "EXTRACT(SECOND FROM($field))";
case ('WEEK'): // ISO week number for date
switch ($this->db_type) {
case ('mysql'):
case ('mysqli'):
// WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
return "WEEK($field, 3)";
case ('pgsql'):
return "EXTRACT(WEEK FROM($field))";
}
case ('DOW'):
switch ($this->db_type) {
case ('mysql'):
case ('mysqli'):
// mysql returns 1 for Sunday through 7 for Saturday
// php date functions and postgres use 0 for Sunday and 6 for Saturday
return "INTEGER(DAYOFWEEK($field) - 1)";
case ('pgsql'):
return "EXTRACT(DOW FROM($field))";
}
case ('DOY'):
switch ($this->db_type) {
case ('mysql'):
case ('mysqli'):
return "DAYOFYEAR($field)";
case ('pgsql'):
return "EXTRACT(DOY FROM($field))";
}
}
}
/**
* Create a where clause to compare a complete date field to a complete date value.
*
* @param string $type
* The type of value we're comparing to, could be another field
* or a date value.
* @param string $field
* The db table and field name, like "$table.$field".
* @param string $operator
* The db comparison operator to use, like '='.
* @param int $value
* The value to compare the extracted date part to, could be a
* field name or a date string or NOW().
* @return
* SQL for the where clause for this operation.
*/
function sql_where_date($type, $field, $operator, $value, $adjustment = NULL) {
$type = drupal_strtoupper($type);
if (drupal_strtoupper($value) == 'NOW') {
$value = $this->sql_field('NOW', $adjustment);
}
elseif ($type == 'FIELD') {
$value = $this->sql_field($value, $adjustment);
}
elseif ($type == 'DATE') {
$date = date_make_date($value, date_default_timezone_name(), DATE_DATETIME);
if (!empty($adjustment)) {
date_modify($date, $adjustment .' seconds');
}
// When comparing a field to a date we can avoid doing timezone
// conversion by altering the comparison date to the db timezone.
// This won't work if the timezone is a field instead of a value.
if (empty($this->db_timezone_field) && empty($this->local_timezone_field) && $this->db_timezone_field != $this->local_timezone_field) {
date_timezone_set($date, timezone_open($this->db_timezone));
$this->local_timezone = $this->db_timezone;
}
$value = "'". date_format_date($date, 'custom', DATE_FORMAT_DATETIME) ."'";
}
if ($this->local_timezone != $this->db_timezone) {
$field = $this->sql_field($field);
}
else {
$field = $this->sql_field($field, 0);
}
return "$field $operator $value";
}
/**
* Create a where clause to compare an extracted part of a field to an integer value.
*
* @param string $part
* The part to extract, YEAR, MONTH, DAY, etc.
* @param string $field
* The db table and field name, like "$table.$field".
* @param string $operator
* The db comparison operator to use, like '='.
* @param int $value
* The integer value to compare the extracted date part to.
* @return
* SQL for the where clause for this operation.
*/
function sql_where_extract($part, $field, $operator, $value, $adjustment = NULL) {
$field = $this->sql_field($field, $adjustment);
return $this->sql_extract($part, $field) ." $operator $value";
}
/**
* Create a where clause to compare a formated field to a formated value.
*
* @param string $format
* The format to use on the date and the value when comparing them.
* @param string $field
* The db table and field name, like "$table.$field".
* @param string $operator
* The db comparison operator to use, like '='.
* @param string $value
* The value to compare the extracted date part to, could be a
* field name or a date string or NOW().
* @return
* SQL for the where clause for this operation.
*/
function sql_where_format($format, $field, $operator, $value, $adjustment = NULL) {
$field = $this->sql_field($field, $adjustment);
return $this->sql_format($format, $field) ." $operator '$value'";
}
/**
* An array of all date parts,
* optionally limited to an array of allowed parts.
*/
function date_parts($limit = NULL) {
$parts = array(
'year' => date_t('Year', 'datetime'), 'month' => date_t('Month', 'datetime'), 'day' => date_t('Day', 'datetime'),
'hour' => date_t('Hour', 'datetime'), 'minute' => date_t('Minute', 'datetime'), 'second' => date_t('Second', 'datetime'),
);
if (!empty($limit)) {
$last = FALSE;
foreach ($parts as $key => $part) {
if ($last) {
unset($parts[$key]);
}
if ($key == $limit) {
$last = TRUE;
}
}
}
return $parts;
}
/**
* Part information.
*
* @param $op
* 'min', 'max', 'format', 'sep', 'empty_now', 'empty_min', 'empty_max'.
* Returns all info if empty.
* @param $part
* 'year', 'month', 'day', 'hour', 'minute', or 'second.
* returns info for all parts if empty.
*/
function part_info($op = NULL, $part = NULL) {
$info = array();
$info['min'] = array(
'year' => 100, 'month' => 1, 'day' => 1,
'hour' => 0, 'minute' => 0, 'second' => 0);
$info['max'] = array(
'year' => 4000, 'month' => 12, 'day' => 31,
'hour' => 23, 'minute' => 59, 'second' => 59);
$info['format'] = array(
'year' => 'Y', 'month' => 'm', 'day' => 'd',
'hour' => 'H', 'minute' => 'i', 'second' => 's');
$info['sep'] = array(
'year' => '', 'month' => '-', 'day' => '-',
'hour' => ' ', 'minute' => ':', 'second' => ':');
$info['empty_now'] = array(
'year' => date('Y'), 'month' => date('m'), 'day' => min('28', date('d')),
'hour' => date('H'), 'minute' => date('i'), 'second' => date('s'));
$info['empty_min'] = array(
'year' => '1000', 'month' => '01', 'day' => '01',
'hour' => '00', 'minute' => '00', 'second' => '00');
$info['empty_max'] = array(
'year' => '9999', 'month' => '12', 'day' => '31',
'hour' => '23', 'minute' => '59', 'second' => '59');
if (!empty($op)) {
if (!empty($part)) {
return $info[$op][$part];
}
else {
return $info[$op];
}
}
return $info;
}
/**
* Create a complete datetime value out of an
* incomplete array of selected values.
*
* For example, array('year' => 2008, 'month' => 05) will fill
* in the day, hour, minute and second with the earliest possible
* values if type = 'min', the latest possible values if type = 'max',
* and the current values if type = 'now'.
*/
function complete_date($selected, $type = 'now') {
if (empty($selected)) {
return '';
}
// Special case for weeks.
if (array_key_exists('week', $selected)) {
$dates = date_week_range($selected['week'], $selected['year']);
switch ($type) {
case 'empty_now':
case 'empty_min':
case 'min':
return date_format($dates[0], 'Y-m-d H:i:s');
case 'empty_max':
case 'max':
return date_format($dates[1], 'Y-m-d H:i:s');
default:
return;
}
}
$compare = array_merge($this->part_info('empty_'. $type), $selected);
// If this is a max date, make sure the last day of
// the month is the right one for this date.
if ($type == 'max') {
$compare['day'] = date_days_in_month($compare['year'], $compare['month']);
}
$value = '';
$separators = $this->part_info('sep');
foreach ($this->date_parts() as $key => $name) {
$value .= $separators[$key] . (!empty($selected[$key]) ? $selected[$key] : $compare[$key]);
}
return $value;
}
/**
* Convert a format string into help text,
* i.e. 'Y-m-d' becomes 'YYYY-MM-DD'.
*
* @param unknown_type $format
* @return unknown
*/
function format_help($format) {
$replace = array(
'Y' => 'YYYY', 'm' => 'MM', 'd' => 'DD',
'H' => 'HH', 'i' => 'MM', 's' => 'SS', '\T' => 'T');
return strtr($format, $replace);
}
/**
* A function to test the validity of various date parts
*/
function part_is_valid($value, $type) {
if ( !preg_match('/^[0-9]*$/', $value) ) {
return FALSE;
}
$value = intval($value);
if ($value <= 0) return FALSE;
switch ($type) {
case 'year':
if ($value < DATE_MIN_YEAR) return FALSE;
break;
case 'month':
if ($value < 0 || $value > 12) return FALSE;
break;
case 'day':
if ($value < 0 || $value > 31) return FALSE;
break;
case 'week':
if ($value < 0 || $value > 53) return FALSE;
}
return TRUE;
}
function views_formats($granularity, $type = 'sql') {
$formats = array('display', 'sql');
// Start with the site long date format and add seconds to it
$long = str_replace(':i', ':i:s', variable_get('date_format_long', 'l, F j, Y - H:i'));
switch ($granularity) {
case ('year'):
$formats['display'] = 'Y';
$formats['sql'] = 'Y';
break;
case ('month'):
$formats['display'] = date_limit_format($long, array('year', 'month'));
$formats['sql'] = 'Y-m';
break;
case ('day'):
$formats['display'] = date_limit_format($long, array('year', 'month', 'day'));
$formats['sql'] = 'Y-m-d';
break;
case ('hour'):
$formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour'));
$formats['sql'] = 'Y-m-d\TH';
break;
case ('minute'):
$formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute'));
$formats['sql'] = 'Y-m-d\TH:i';
break;
case ('second'):
$formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute', 'second'));
$formats['sql'] = 'Y-m-d\TH:i:s';
break;
case ('week'):
$formats['display'] = 'F j Y (W)';
$formats['sql'] = 'Y-\WW';
break;
}
return $formats[$type];
}
function granularity_form($granularity) {
$form = array(
'#title' => t('Granularity'),
'#type' => 'radios',
'#default_value' => $granularity,
'#options' => $this->date_parts(),
);
return $form;
}
/**
* Parse date parts from an ISO date argument.
*
* Based on ISO 8601 date duration and time interval standards.
*
* See http://en.wikipedia.org/wiki/ISO_8601#Week_dates for definitions of ISO weeks.
* See http://en.wikipedia.org/wiki/ISO_8601#Duration for definitions of ISO duration and time interval.
*
* Parses a value like 2006-01-01--2006-01-15, or 2006-W24, or @P1W.
* Separate from and to dates or date and period with a double hyphen (--).
*
* The 'to' portion of the argument can be eliminated if it is the same as the 'from' portion.
* Use @ instead of a date to substitute in the current date and time.
*
* Use periods (P1H, P1D, P1W, P1M, P1Y) to get next hour/day/week/month/year from now.
* Use date before P sign to get next hour/day/week/month/year from that date.
* Use period then date to get a period that ends on the date.
*
*/
function arg_parts($argument) {
$values = array();
// Keep mal-formed arguments from creating errors.
if (empty($argument) || is_array($argument)) {
return array('date' => array(), 'period' => array());
}
$fromto = explode('--', $argument);
foreach ($fromto as $arg) {
$parts = array();
if ($arg == '@') {
$parts['date'] = date_array(date_now());
}
elseif (preg_match('/(\d{4})?-?(W)?(\d{1,2})?-?(\d{1,2})?[T\s]?(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?/', $arg, $matches)) {
$date = array();
if (!empty($matches[1])) $date['year'] = $matches[1];
if (!empty($matches[3])) {
if (empty($matches[2])) {
$date['month'] = $matches[3];
}
else {
$date['week'] = $matches[3];
}
}
if (!empty($matches[4])) $date['day'] = $matches[4];
if (!empty($matches[5])) $date['hour'] = $matches[5];
if (!empty($matches[6])) $date['minute'] = $matches[6];
if (!empty($matches[7])) $date['second'] = $matches[7];
$parts['date'] = $date;
}
if (preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $arg, $matches)) {
$period = array();
if (!empty($matches[1])) $period['year'] = str_replace('Y', '', $matches[1]);
if (!empty($matches[2])) $period['month'] = str_replace('M', '', $matches[2]);
if (!empty($matches[3])) $period['week'] = str_replace('W', '', $matches[3]);
if (!empty($matches[4])) $period['day'] = str_replace('D', '', $matches[4]);
if (!empty($matches[6])) $period['hour'] = str_replace('H', '', $matches[6]);
if (!empty($matches[7])) $period['minute'] = str_replace('M', '', $matches[7]);
if (!empty($matches[8])) $period['second'] = str_replace('S', '', $matches[8]);
$parts['period'] = $period;
}
$values[] = $parts;
}
return $values;
}
/**
* Convert strings like '+1 day' to the ISO equivalent, like 'P1D'.
*/
function arg_replace($arg) {
if (!preg_match('/([+|-])\s?([0-9]{1,32})\s?([day(s)?|week(s)?|month(s)?|year(s)?|hour(s)?|minute(s)?|second(s)?]{1,10})/', $arg, $results)) {
return str_replace('now', '@', $arg);
}
$direction = $results[1];
$count = $results[2];
$item = $results[3];
$replace = array(
'now' => '@',
'+' => 'P',
'-' => 'P-',
'years' => 'Y',
'year' => 'Y',
'months' => 'M',
'month' => 'M',
'weeks' => 'W',
'week' => 'W',
'days' => 'D',
'day' => 'D',
'hours' => 'H',
'hour' => 'H',
'minutes' => 'M',
'minute' => 'M',
'seconds' => 'S',
'second' => 'S',
' ' => '',
' ' => '',
);
$prefix = in_array($item, array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second')) ? 'T' : '';
return $prefix . strtr($direction, $replace) . $count . strtr($item, $replace);
}
/**
* Use the parsed values from the ISO argument to determine the
* granularity of this period.
*/
function arg_granularity($arg) {
$granularity = '';
$parts = $this->arg_parts($arg);
$date = !empty($parts[0]['date']) ? $parts[0]['date'] : (!empty($parts[1]['date']) ? $parts[1]['date'] : array());
foreach ($date as $key => $part) {
$granularity = $key;
}
return $granularity;
}
/**
* Use the parsed values from the ISO argument to determine the
* min and max date for this period.
*/
function arg_range($arg) {
// Parse the argument to get its parts
$parts = $this->arg_parts($arg);
// Build a range from a period-only argument (assumes the min date is now.)
if (empty($parts[0]['date']) && !empty($parts[0]['period']) && (empty($parts[1]))) {
$min_date = date_now();
$max_date = drupal_clone($min_date);
foreach ($parts[0]['period'] as $part => $value) {
date_modify($max_date, "+$value $part");
}
date_modify($max_date, '-1 second');
return array($min_date, $max_date);
}
// Build a range from a period to period argument
if (empty($parts[0]['date']) && !empty($parts[0]['period']) && !empty($parts[1]['period'])) {
$min_date = date_now();
$max_date = drupal_clone($min_date);
foreach ($parts[0]['period'] as $part => $value) {
date_modify($min_date, "+$value $part");
}
date_modify($min_date, '-1 second');
foreach ($parts[1]['period'] as $part => $value) {
date_modify($max_date, "+$value $part");
}
date_modify($max_date, '-1 second');
return array($min_date, $max_date);
}
if (!empty($parts[0]['date'])) {
$value = date_fuzzy_datetime($this->complete_date($parts[0]['date'], 'min'));
$min_date = date_make_date($value, date_default_timezone_name(), DATE_ISO);
// Build a range from a single date-only argument.
if (empty($parts[1]) || (empty($parts[1]['date']) && empty($parts[1]['period']))) {
$value = date_fuzzy_datetime($this->complete_date($parts[0]['date'], 'max'));
$max_date = date_make_date($value, date_default_timezone_name(), DATE_ISO);
return array($min_date, $max_date);
}
// Build a range from start date + period.
elseif (!empty($parts[1]['period'])) {
foreach ($parts[1]['period'] as $part => $value) {
$max_date = drupal_clone($min_date);
date_modify($max_date, "+$value $part");
}
date_modify($max_date, '-1 second');
return array($min_date, $max_date);
}
}
// Build a range from start date and end date.
if (!empty($parts[1]['date'])) {
$value = date_fuzzy_datetime($this->complete_date($parts[1]['date'], 'max'));
$max_date = date_make_date($value, date_default_timezone_name(), DATE_ISO);
if (isset($min_date)) {
return array($min_date, $max_date);
}
}
// Build a range from period + end date.
if (!empty($parts[0]['period'])) {
$min_date = date_now();
foreach ($parts[0]['period'] as $part => $value) {
date_modify($min_date, "$value $part");
}
return array($min_date, $max_date);
}
// Intercept invalid info and fall back to the current date.
$now = date_now();
return array($now, $now);
}
}

View file

@ -0,0 +1,13 @@
name = Date Locale
description = Allows the site admin to configure multiple formats for date/time display to tailor dates for a specific locale or audience.
package = Date/Time
dependencies[] = date_api
dependencies[] = locale
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,295 @@
<?php
/**
* @file
* Enable different locales to have their own date formats.
*/
/**
* Implementation of hook_init().
*
* Initialize date formats according to the user's current locale.
*/
function date_locale_init() {
global $conf;
global $language;
// Don't do this on the general date and time formats settings page, as we
// want to display the defaults, not the ones specific to the language we're
// currently browsing the site in.
if (!drupal_match_path($_GET['q'], 'admin/settings/date-time/formats')) {
$languages = array($language->language);
if (module_exists('site_country')) {
$country_code = variable_get('site_country_default_country', '');
if (!empty($country_code)) {
$country_language = $language->language . '-' . $country_code;
array_unshift($languages, $country_language);
}
}
drupal_alter('date_format_languages', $languages);
// Setup appropriate date formats for this locale.
$formats = date_locale_get_locale_date_format($languages);
foreach ($formats as $format_type => $format) {
$conf[$format_type] = $format;
}
}
}
/**
* Implementation of hook_menu().
*/
function date_locale_menu() {
$items = array();
$items['admin/settings/date-time/locale'] = array(
'title' => 'Locale date settings',
'description' => 'Configure date formats for each locale',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array('date_locale_format_form'),
'access arguments' => array('administer site configuration'),
'weight' => 1,
);
return $items;
}
/**
* Select locale date format details from database.
*
* @param $languages
* An array of language codes.
* @return
* An array of date formats.
*/
function date_locale_get_locale_date_format($languages) {
$formats = array();
// Get list of different format types.
$format_types = date_get_format_types();
$short_default = variable_get('date_format_short', 'm/d/Y - H:i');
// Loop through each language until we find one with some date formats
// configured.
foreach ($languages as $language) {
$date_formats = date_format_locale($language);
if (!empty($date_formats)) {
// We have locale-specific date formats, so check for their types. If
// we're missing a type, use the default setting instead.
foreach ($format_types as $type => $type_info) {
if (isset($date_formats[$type])) {
$format = $date_formats[$type];
// If format exists for this language, use it.
if (!empty($format)) {
$formats['date_format_' . $type] = $format;
}
// Otherwise get default variable setting. If this is not set, default
// to the short format.
else {
$formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
}
}
}
// Return on the first match.
return $formats;
}
}
// No locale specific formats found, so use defaults.
$system_types = array('short', 'medium', 'long');
// Handle system types separately as they have defaults if no variable exists.
$formats['date_format_short'] = $short_default;
$formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i');
$formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i');
// For non-system types, get the default setting, otherwise use the short
// format.
foreach ($format_types as $type => $type_info) {
if (!in_array($type, $system_types)) {
$formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
}
}
return $formats;
}
/**
* Display list of enabled languages to configure date formats for.
*/
function date_locale_format_form($form_state) {
$form = array();
if (!isset($form_state['values'])) {
$step = 'languages';
}
else {
$step = 'config';
}
$form['step'] = array(
'#type' => 'value',
'#value' => $step,
);
// Form part 1: show language selection.
if ($step == 'languages') {
// Get list of languages.
$languages = locale_language_list('native');
// If site_country module is enabled, add country specific languages to
// languages array.
if (module_exists('site_country')) {
$country_code = variable_get('site_country_default_country', '');
if (!empty($country_code)) {
foreach ($languages as $langcode => $name) {
$country_language = $langcode . '-' . $country_code;
if (drupal_strlen($langcode) == 2 && !in_array($country_language, array_keys($languages))) {
$languages[$country_language] = "$name ($country_code)";
}
}
}
}
$form['langcode'] = array(
'#title' => t('Language'),
'#type' => 'select',
'#options' => $languages,
'#multiple' => FALSE,
);
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
'#submit' => array('date_locale_format_form_language_submit'),
);
}
// Form part 2: show date formats for this language.
else {
// Add Drupal core's system.js and js settings.
date_api_add_system_javascript();
$languages = locale_language_list('native');
$langcode = $form_state['values']['langcode'];
$language_name = $languages[$langcode];
// Display the current language name.
$form['language_information'] = array(
'#value' => t('Date format settings for %language_name', array('%language_name' => $language_name)),
'#prefix' =>'<p style="font-size: 1.2em;">',
'#suffix' =>'</p>',
);
// Get list of date format types.
$types = date_get_format_types();
// Get list of available formats.
$formats = date_get_formats();
$choices = array();
foreach ($formats as $type => $list) {
foreach ($list as $f => $format) {
$choices[$f] = date_format_date(date_now(), 'custom', $f);
}
}
// Get configured formats for each language.
$locale_formats = date_format_locale($langcode);
// Display a form field for each format type.
foreach ($types as $type => $type_info) {
if (!empty($locale_formats) && in_array($type, array_keys($locale_formats))) {
$default = $locale_formats[$type];
}
else {
$default = variable_get('date_format_' . $type, array_shift(array_keys($formats)));
}
include_once('./'. drupal_get_path('module', 'date_api') .'/date_api.admin.inc');
date_api_date_format_select_field($form, $type, $type_info, $default, $choices);
}
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#submit' => array('date_locale_format_form_formats_submit'),
);
$form['buttons']['cancel'] = array(
'#type' => 'submit',
'#value' => t('Cancel'),
'#submit' => array('date_locale_format_form_formats_cancel'),
);
}
return $form;
}
/**
* Submit handler for choosing a language on the date_locale_format_form.
*
* @param $form
* Array, containing the form structure.
* @param &$form_state
* The 'rebuild' key inside $form_state['rebuild'] structure, overrides the
* 'redirect' key: when it is set to TRUE, the form will be rebuilt from
* scratch and displayed on screen.
*/
function date_locale_format_form_language_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
$form_state['storage']['langcode'] = $form_state['values']['langcode'];
}
/**
* Submit handler for choosing a language on the date_locale_format_form.
*/
function date_locale_format_form_formats_submit($form, &$form_state) {
$langcode = $form_state['storage']['langcode'];
// Get list of date format types.
$types = date_get_format_types();
foreach ($types as $type => $type_info) {
$format = $form_state['values']['date_format_' . $type];
if ($format == 'custom') {
$format = $form_state['values']['date_format_' . $type . '_custom'];
}
date_locale_locale_format_save($langcode, $type, $format);
}
drupal_set_message(t('Configuration saved.'));
$form_state['storage'] = FALSE;
$form_state['rebuild'] = FALSE;
$form_state['redirect'] = 'admin/settings/date-time/locale';
}
/**
* 'Cancel' button handler for choosing a language on the
* date_locale_format_form.
*/
function date_locale_format_form_formats_cancel($form, &$form_state) {
$form_state['storage'] = FALSE;
$form_state['rebuild'] = FALSE;
$form_state['redirect'] = 'admin/settings/date-time/locale';
}
/**
* Save locale specific date formats to the database.
*
* @param $langcode
* Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g.
* 'en-CA'.
* @param $type
* Date format type, e.g. 'short', 'medium'.
* @param $format
* The date format string.
*/
function date_locale_locale_format_save($langcode, $type, $format) {
$locale_format = array();
$locale_format['language'] = $langcode;
$locale_format['type'] = $type;
$locale_format['format'] = $format;
$is_existing = db_result(db_query("SELECT COUNT(*) FROM {date_format_locale} WHERE language = '%s' AND type = '%s'", $langcode, $type));
if ($is_existing) {
$keys = array('type', 'language');
drupal_write_record('date_format_locale', $locale_format, $keys);
}
else {
drupal_write_record('date_format_locale', $locale_format);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
name = Date PHP4
description = Emulate PHP 5.2 date functions in PHP 4.x, PHP 5.0, and PHP 5.1. Required when using the Date API with PHP versions less than PHP 5.2.
package = Date/Time
dependencies[] = date_api
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,16 @@
<?php
function date_php4_set_variables() {
variable_set('date_max_year', 3000);
variable_set('date_min_year', 100);
variable_set('date_php_min_year', 1971);
}
function date_php4_install() {
$ret = array();
date_php4_set_variables();
return $ret;
}
function date_php4_enable() {
date_php4_set_variables();
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Include PHP 4 date handling files on systems where native date and
* timezone handling won't work. This module will create the functions
* date_create(), date_offset_get(), etc. that will work in PHP 4.
*
* This module is not needed on systems using PHP 5.2+ and won't be needed once
* Drupal requires PHP 5.2.
*/
require_once('./'. drupal_get_path('module', 'date_php4') .'/date_php4.inc');
/**
* Implementation of hook_perm().
*/
function date_php4_perm() {
return array('administer date_php4 settings');
}
/**
* Implementation of hook_menu().
*/
function date_php4_menu() {
$items = array();
$items['admin/settings/date_php4'] = array(
'title' => 'Date PHP4',
'description' => 'Date PHP4 setup.',
'access arguments' => array('administer date_php4 settings'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_php4_settings_form'),
'type' => MENU_NORMAL_ITEM,
'weight' => 6,
);
return $items;
}
/**
* Timezone handling.
*/
function date_php4_settings_form() {
drupal_set_title(t('Date PHP4 Settings'));
$form['date_use_server_zone'] = array(
'#type' => 'select',
'#options' => array(TRUE, t('TRUE'), FALSE => t('FALSE')),
'#default_value' => variable_get('date_use_server_zone', FALSE),
'#title' => t('Use PHP default timezone'),
'#description' => t('Getting date computations working correctly in PHP versions earlier than PHP 5.2 involves extra computations that add a lot of overhead. These computations are needed because the timezone PHP uses on date computations may not match the site or user timezone or other date-specific timezones. We can speed processing up if we assume that PHP is using the correct timezone, but need to do more time-intensive processing if it is not. If timezone adjustments do not seem to be working correctly in your setup, you can set this option to FALSE to force the system to use the more accurate, but slower, timezone computations.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
function date_php4_settings_form_submit($form, &$form_state) {
variable_set('date_use_server_zone', $form_state['values']['date_use_server_zone']);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,442 @@
<?php
/**
* Date Library, extended date functions.
* File is included only when needed.
*/
/**
* @ingroup adodb
* @{
*/
/**
* The following functions are low level functions that implements pre 1970
* to post 2038 versions of native php date functions. Will handle dates from
* the year 100 to the year 3000. Uses native php date functions when possible,
* alterate methods when native functions won't work.
*
* Altered the original ADODB code to split it between the high level
* functions which are used when pre-1970 and post-2038 dates are not needed
* and this large file which is only parsed for dates that are out of range
* for native php date handling.
*
* Replace native php functions:
* getdate() with date_getdate()
* date() with date_date()
* gmdate() with date_gmdate()
* mktime() with date_mktime()
* gmmktime() with gmdate_mktime()
*
* The following functions were derived from code obtained from
* http://phplens.com/phpeverywhere/adodb_date_library, licensed as follows:
*
* COPYRIGHT(c) 2003-2005 John Lim
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of the BSD License.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Low-level function that returns the getdate() array for pre-1970
* and post-2038 dates.
*
* We have a special$fast flag, which if set to true, will return fewer
* array values, and is much faster as it does not calculate dow, etc.
*
* @param $timestamp a unix timestamp
* @param $timezone name
* Use 'UTC' to avoid timezone conversion.
*/
function _date_getdate($timestamp = FALSE, $timezone = FALSE) {
static $YRS;
if ($timezone === FALSE) {
$timezone = date_default_timezone_name();
}
$timestamp_in = $timestamp;
$_day_power = 86400;
$_hour_power = 3600;
$_min_power = 60;
if ($timestamp < -12219321600) $timestamp -= 86400*10; // if 15 Oct 1582 or earlier, gregorian correction
$_month_table_normal = array("", 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_month_table_leap = array("", 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$d366 = $_day_power * 366;
$d365 = $_day_power * 365;
if ($timestamp < 0) {
if (empty($YRS)) $YRS = array(
1970 => 0,
1960 => -315619200,
1950 => -631152000,
1940 => -946771200,
1930 => -1262304000,
1920 => -1577923200,
1910 => -1893456000,
1900 => -2208988800,
1890 => -2524521600,
1880 => -2840140800,
1870 => -3155673600,
1860 => -3471292800,
1850 => -3786825600,
1840 => -4102444800,
1830 => -4417977600,
1820 => -4733596800,
1810 => -5049129600,
1800 => -5364662400,
1790 => -5680195200,
1780 => -5995814400,
1770 => -6311347200,
1760 => -6626966400,
1750 => -6942499200,
1740 => -7258118400,
1730 => -7573651200,
1720 => -7889270400,
1710 => -8204803200,
1700 => -8520336000,
1690 => -8835868800,
1680 => -9151488000,
1670 => -9467020800,
1660 => -9782640000,
1650 => -10098172800,
1640 => -10413792000,
1630 => -10729324800,
1620 => -11044944000,
1610 => -11360476800,
1600 => -11676096000);
// The valid range of a 32bit signed timestamp is typically from
// Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT
//
$lastsecs = 0;
$lastyear = 1970;
foreach ($YRS as $year => $secs) {
if ($timestamp >= $secs) {
$a = $lastyear;
break;
}
$lastsecs = $secs;
$lastyear = $year;
}
$timestamp -= $lastsecs;
if (!isset($a)) $a = $lastyear;
for (; --$a >= 0;) {
$lastd = $timestamp;
if ($leap = date_is_leap_year($a)) $timestamp += $d366;
else $timestamp += $d365;
if ($timestamp >= 0) {
$year = $a;
break;
}
}
$secs_in_year = 86400 * ($leap ? 366 : 365) + $lastd;
$timestamp = $lastd;
$mtab = ($leap) ? $_month_table_leap : $_month_table_normal;
for ($a = 13 ; --$a > 0;) {
$lastd = $timestamp;
$timestamp += $mtab[$a] * $_day_power;
if ($timestamp >= 0) {
$month = $a;
$ndays = $mtab[$a];
break;
}
}
$timestamp = $lastd;
$day = $ndays + ceil(($timestamp+1) / ($_day_power));
$timestamp += ($ndays - $day+1)* $_day_power;
$hour = floor($timestamp/$_hour_power);
}
else {
if ($timezone != 'UTC') {
$timestamp += date_get_gmt_diff_ts($timestamp, $timezone);
}
for ($a = 1970 ;; $a++) {
$lastd = $timestamp;
if ($leap = date_is_leap_year($a)) $timestamp -= $d366;
else $timestamp -= $d365;
if ($timestamp < 0) {
$year = $a;
break;
}
}
$secs_in_year = $lastd;
$timestamp = $lastd;
$mtab = ($leap) ? $_month_table_leap : $_month_table_normal;
for ($a = 1 ; $a <= 12; $a++) {
$lastd = $timestamp;
$timestamp -= $mtab[$a] * $_day_power;
if ($timestamp < 0) {
$month = $a;
$ndays = $mtab[$a];
break;
}
}
$timestamp = $lastd;
$day = ceil(($timestamp + 1) / $_day_power);
$timestamp = $timestamp - ($day - 1) * $_day_power;
$hour = floor($timestamp / $_hour_power);
}
$timestamp -= $hour * $_hour_power;
$min = floor($timestamp / $_min_power);
$secs = $timestamp - $min * $_min_power;
$dow = date_dow($day, $month, $year);
return array(
'second' => $secs,
'minute' => $min,
'hour' => $hour,
'day' => $day,
'wday' => $dow,
'month' => $month,
'year' => $year,
'yday' => floor($secs_in_year / $_day_power),
'weekday' => gmdate('l', ($_day_power * (3 + $dow))),
'leap' => $leap,
'ndays' => $ndays,
'month_name' => gmdate('F', mktime(0, 0, 0, $month, 2, 1971)),
0 => $timestamp_in
);
}
/**
* Low level function to create date() for pre-1970 and post-2038 dates.
*
* @param $format a format string for the result
* @param $timestamp a unix timestamp
* @param $timezone name
* Use 'UTC' to avoid timezone conversion.
*/
function _date_date($format, $timestamp = FALSE, $timezone = FALSE) {
if ($timezone === FALSE) {
$timezone = date_default_timezone_name();
}
$_day_power = 86400;
$arr = _date_getdate($timestamp, $timezone);
$year = $arr['year'];
$month = $arr['month'];
$day = $arr['day'];
$hour = $arr['hour'];
$min = $arr['minute'];
$secs = $arr['second'];
$max = strlen($format);
$dates = '';
/*
at this point, we have the following integer vars to manipulate:
$year, $month, $day, $hour, $min, $secs
*/
for ($i = 0; $i < $max; $i++) {
switch ($format[$i]) {
case 'T': $dates .= date('T');break;
// YEAR
case 'L': $dates .= $arr['leap'] ? '1' : '0'; break;
case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
// 4.3.11 uses '04 Jun 2004'
// 4.3.8 uses ' 4 Jun 2004'
$dates .= gmdate('D', $_day_power*(3 + date_dow($day, $month, $year))) .', '.
($day < 10 ? '0'. $day : $day) .' '. date('M', mktime(0, 0, 0, $month, 2, 1971)) .' '. $year .' ';
if ($hour < 10) $dates .= '0'. $hour; else $dates .= $hour;
if ($min < 10) $dates .= ':0'. $min; else $dates .= ':'. $min;
if ($secs < 10) $dates .= ':0'. $secs; else $dates .= ':'. $secs;
$gmt = date_get_gmt_diff_ts($timestamp, $timezone);
$dates .= sprintf(' %s%04d', ($gmt >= 0) ? '+' : '-', abs($gmt) / 36); break;
case 'Y': $dates .= date_pad($year, 4); break;
case 'y': $dates .= drupal_substr($year, strlen($year)-2, 2); break;
// MONTH
case 'm': if ($month<10) $dates .= '0'. $month; else $dates .= $month; break;
case 'Q': $dates .= ($month + 3)>>2; break;
case 'n': $dates .= $month; break;
case 'M': $dates .= date('M', mktime(0, 0, 0, $month, 2, 1971)); break;
case 'F': $dates .= date('F', mktime(0, 0, 0, $month, 2, 1971)); break;
// DAY
case 't': $dates .= $arr['ndays']; break;
case 'z': $dates .= $arr['yday']; break;
case 'w': $dates .= date_dow($day, $month, $year); break;
case 'l': $dates .= gmdate('l', $_day_power*(3 + date_dow($day, $month, $year))); break;
case 'D': $dates .= gmdate('D', $_day_power*(3 + date_dow($day, $month, $year))); break;
case 'j': $dates .= $day; break;
case 'd': if ($day<10) $dates .= '0'. $day; else $dates .= $day; break;
case 'S':
$d10 = $day % 10;
if ($d10 == 1) $dates .= 'st';
elseif ($d10 == 2 && $day != 12) $dates .= 'nd';
elseif ($d10 == 3) $dates .= 'rd';
else $dates .= 'th';
break;
// HOUR
case 'Z':
$dates .= -date_get_gmt_diff_ts($timestamp, $timezone);
break;
case 'O':
$gmt = date_get_gmt_diff_ts($timestamp, $timezone);
$dates .= sprintf('%s%04d', ($gmt<0)?'+':'-', abs($gmt)/36);
break;
case 'H':
if ($hour < 10) $dates .= '0'. $hour;
else $dates .= $hour;
break;
case 'h':
if ($hour > 12) $hh = $hour - 12;
else {
if ($hour == 0) $hh = '12';
else $hh = $hour;
}
if ($hh < 10) $dates .= '0'. $hh;
else $dates .= $hh;
break;
case 'G':
$dates .= $hour;
break;
case 'g':
if ($hour > 12) $hh = $hour - 12;
else {
if ($hour == 0) $hh = '12';
else $hh = $hour;
}
$dates .= $hh;
break;
// MINUTES
case 'i': if ($min < 10) $dates .= '0'. $min; else $dates .= $min; break;
// SECONDS
case 'U': $dates .= $timestamp; break;
case 's': if ($secs < 10) $dates .= '0'. $secs; else $dates .= $secs; break;
// AM/PM
// Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
case 'a':
if ($hour>=12) $dates .= 'pm';
else $dates .= 'am';
break;
case 'A':
if ($hour>=12) $dates .= 'PM';
else $dates .= 'AM';
break;
default:
$dates .= $format[$i]; break;
// ESCAPE
case "\\":
$i++;
if ($i < $max) $dates .= $format[$i];
break;
}
}
return $dates;
}
/**
* Low level function to create mktime() for pre-1970 and post 2038 dates.
*
* @param $hr the hour
* @param $min the minute
* @param $sec the second
* @param $mon the month
* @param $day the day
* @param $year the year
* @param $timezone name
* Use 'UTC' to avoid timezone conversion.
*/
function _date_mktime($hr, $min, $sec, $mon = FALSE, $day = FALSE, $year = FALSE, $timezone = FALSE) {
if ($timezone === FALSE) {
$timezone = date_default_timezone_name();
}
/*
# disabled because some people place large values in $sec.
# however we need it for $mon because we use an array...
$hr = intval($hr);
$min = intval($min);
$sec = intval($sec);
*/
$mon = intval($mon);
$day = intval($day);
$year = intval($year);
$year = date_year_digit_check($year);
if ($mon > 12) {
$y = floor(($mon-1) / 12);
$year += $y;
$mon -= $y * 12;
}
elseif ($mon < 1) {
$y = ceil((1-$mon) / 12);
$year -= $y;
$mon += $y * 12;
}
$_day_power = 86400;
$_hour_power = 3600;
$_min_power = 60;
$_month_table_normal = array("", 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_month_table_leap = array("", 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_total_date = 0;
if ($year >= 1970) {
$year_in = $year;
for ($a = 1970 ; $a <= $year; $a++) {
$leap = date_is_leap_year($a);
if ($leap == true) {
$loop_table = $_month_table_leap;
$_add_date = 366;
}
else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a < $year) {
$_total_date += $_add_date;
}
else {
for ($b=1; $b<$mon; $b++) {
$_total_date += $loop_table[$b];
}
}
}
$_total_date +=$day-1;
$ret = ($_total_date * $_day_power) + ($hr * $_hour_power) + ($min * $_min_power) + $sec;
$ret -= date_get_gmt_diff_ts($ret, $timezone);
}
else {
for ($a = 1969 ; $a >= $year; $a--) {
$leap = date_is_leap_year($a);
if ($leap == true) {
$loop_table = $_month_table_leap;
$_add_date = 366;
}
else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a > $year) { $_total_date += $_add_date;
}
else {
for ($b = 12;$b>$mon;$b--) {
$_total_date += $loop_table[$b];
}
}
}
$_total_date += $loop_table[$mon] - $day;
$_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
$_day_time = $_day_power - $_day_time;
$ret = -( $_total_date * $_day_power + $_day_time);
if ($ret < -12220185600) $ret += 10*86400; // if earlier than 5 Oct 1582 - gregorian correction
elseif ($ret < -12219321600) $ret = -12219321600; // if in limbo, reset to 15 Oct 1582.
}
return $ret;
}
/**
* @} End of ingroup "adodb".
*/

View file

@ -0,0 +1,66 @@
<?php
// These are timezones that exist in the date_timezone_names() array
// that do not exist in the timezone_abbreviations_list(), so we have
// to remove them.
$missing_timezone_data = array(
1 => 'Africa/Asmara',
2 => 'Africa/Bujumbura',
3 => 'Africa/Kinshasa',
4 => 'Africa/Lome',
5 => 'Africa/Lubumbashi',
6 => 'America/Argentina/Buenos_Aires',
7 => 'America/Argentina/Catamarca',
8 => 'America/Argentina/ComodRivadavia',
9 => 'America/Argentina/Cordoba',
10 => 'America/Argentina/Jujuy',
11 => 'America/Argentina/La_Rioja',
12 => 'America/Argentina/Mendoza',
13 => 'America/Argentina/Rio_Gallegos',
14 => 'America/Argentina/San_Juan',
15 => 'America/Argentina/Tucuman',
16 => 'America/Argentina/Ushuaia',
17 => 'America/Atikokan',
18 => 'America/Bahia',
19 => 'America/Blanc-Sablon',
20 => 'America/Campo_Grande',
21 => 'America/Coral_Harbour',
22 => 'America/Indiana/Petersburg',
23 => 'America/Indiana/Vincennes',
24 => 'America/Indiana/Winamac',
25 => 'America/Moncton',
26 => 'America/North_Dakota/New_Salem',
27 => 'America/Resolute',
28 => 'America/Toronto',
29 => 'Antarctica/Rothera',
30 => 'Asia/Dili',
31 => 'Asia/Macau',
32 => 'Asia/Makassar',
33 => 'Asia/Oral',
34 => 'Asia/Qyzylorda',
35 => 'Asia/Samarkand',
36 => 'Asia/Tehran',
37 => 'Atlantic/Faroe',
38 => 'Atlantic/Jan_Mayen',
39 => 'Atlantic/South_Georgia',
40 => 'Australia/Currie',
41 => 'Australia/Eucla',
42 => 'Europe/Guernsey',
43 => 'Europe/Isle_of_Man',
44 => 'Europe/Jersey',
45 => 'Europe/Mariehamn',
46 => 'Europe/Podgorica',
47 => 'Europe/Volgograd',
48 => 'Indian/Christmas',
49 => 'Indian/Cocos',
50 => 'Pacific/Fakaofo',
51 => 'Pacific/Funafuti',
52 => 'Pacific/Johnston',
53 => 'Pacific/Palau',
54 => 'Pacific/Ponape',
55 => 'Pacific/Port_Moresby',
56 => 'Pacific/Tarawa',
57 => 'Pacific/Truk',
58 => 'Pacific/Wake',
59 => 'Pacific/Wallis',
60 => 'Pacific/Yap',
);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,15 @@
; $Id $
name = Date Popup
description = Enables jquery popup calendars and time entry widgets for selecting dates and times.
dependencies[] = date_api
dependencies[] = date_timezone
dependencies[] = jquery_ui
package = Date/Time
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,120 @@
<?php
/**
* Implementation of hook_requirements().
* Added to be sure the Date API version matches this code so invalid
* functions are not called.
*/
function date_popup_requirements($phase) {
$requirements = array();
$t = get_t();
switch ($phase) {
case 'runtime':
if (!module_exists('jquery_ui')) {
$requirements['date_popup_jquery_ui'] = array(
'title' => $t('Date Popup requirements'),
'value' => $t('The Date Popup module needs code added by the <a href="http://drupal.org/project/jquery_ui">jQuery UI module.</a> Install that module as soon as possible.'),
'severity' => REQUIREMENT_WARNING,
);
}
break;
}
return $requirements;
}
/**
* Implementation of hook_install().
*/
function date_popup_install() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
if (module_exists('date_repeat')) {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s' OR widget_type='%s'", 'date_popup', 'date_popup_repeat');
}
else {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s'", 'date_popup');
}
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_uninstall().
*/
function date_popup_uninstall() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
if (module_exists('date_repeat')) {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s' OR widget_type='%s'", 'date_popup', 'date_popup_repeat');
}
else {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s'", 'date_popup');
}
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_enable().
*/
function date_popup_enable() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
if (module_exists('date_repeat')) {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s' OR widget_type='%s'", 'date_popup', 'date_popup_repeat');
}
else {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s'", 'date_popup');
}
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_disable().
*/
function date_popup_disable() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
if (module_exists('date_repeat')) {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s' OR widget_type='%s'", 'date_popup', 'date_popup_repeat');
}
else {
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s'", 'date_popup');
}
content_clear_type_cache(TRUE);
}
}
/**
* Update default path for date popup css
*/
function date_popup_update_6001() {
$ret = array();
$version = function_exists('jquery_ui_get_version') ? jquery_ui_get_version() : '1.6';
switch ($version) {
case '1.6':
$path = drupal_get_path('module', 'date_popup') . '/themes/datepicker.css';
break;
default:
$path = drupal_get_path('module', 'date_popup') . '/themes/datepicker.1.7.css';
break;
}
variable_set('date_popup_css_file', $path);
if (!module_exists('jquery_ui')) {
drupal_set_message('The jQuery UI module is now required when using Date Popup. See the <a href="http://drupal.org/project/jquery_ui">jQuery UI</a> page for installation instructions.', 'error');
}
return $ret;
}

View file

@ -0,0 +1,33 @@
/**
* Attaches the calendar behavior to all required fields
*/
Drupal.behaviors.date_popup = function (context) {
for (var id in Drupal.settings.datePopup) {
$('#'+ id).bind('focus', Drupal.settings.datePopup[id], function(e) {
if (!$(this).hasClass('date-popup-init')) {
var datePopup = e.data;
// Explicitely filter the methods we accept.
switch (datePopup.func) {
case 'datepicker':
$(this)
.datepicker(datePopup.settings)
.addClass('date-popup-init')
$(this).click(function() {
$(this).focus();
});
break;
case 'timeEntry':
$(this)
.timeEntry(datePopup.settings)
.addClass('date-popup-init')
$(this).click(function() {
$(this).focus();
});
break;
}
}
});
}
};

View file

@ -0,0 +1,685 @@
<?php
/**
* @file
* A module to enable jquery calendar and time entry popups.
* Requires the Date API.
*
* Add a type of #date_popup to any date, time, or datetime field that will
* use this popup. Set #date_format to the way the date should be presented
* to the user in the form. Set #default_value to be a date in the local
* timezone, and note the timezone name in #date_timezone.
*
* The element will create two textfields, one for the date and one for the
* time. The date textfield will include a jQuery popup calendar date picker,
* and the time textfield uses a jQuery timepicker.
*
* If no time elements are included in the format string, only the date
* textfield will be created. If no date elements are included in the format
* string, only the time textfield, will be created.
*
*/
/**
* Load needed files.
*/
function date_popup_load() {
static $loaded = FALSE;
if ($loaded) {
return;
}
$path = drupal_get_path('module', 'date_popup');
if (module_exists('jquery_ui')) {
jquery_ui_add('ui.datepicker');
global $language;
if ($language->language != 'en') {
jquery_ui_add("i18n/ui.datepicker-{$language->language}");
}
}
if (variable_get('date_popup_timepicker', 'default') == 'default') {
drupal_add_js($path .'/lib/jquery.timeentry.pack.js');
}
$loaded = TRUE;
}
function date_popup_css_default() {
if (!module_exists('jquery_ui')) {
return '';
}
$version = jquery_ui_get_version();
$jquery_ui_path = drupal_get_path('module', 'jquery_ui');
switch ($version) {
case '1.6':
return drupal_get_path('module', 'date_popup') .'/themes/datepicker.css';
default:
return drupal_get_path('module', 'date_popup') .'/themes/datepicker.1.7.css';
}
}
function date_popup_css_options() {
$paths = array();
if (!module_exists('jquery_ui')) {
return $paths;
}
$version = jquery_ui_get_version();
$jquery_ui_path = jquery_ui_get_path();
switch ($version) {
case '1.6':
$paths[drupal_get_path('module', 'date_popup') .'/themes/datepicker.css'] = t('Date Popup default');
$paths[$jquery_ui_path .'/themes/default/ui.datepicker.css'] = t('jQuery UI default');
break;
default:
$paths[drupal_get_path('module', 'date_popup') .'/themes/datepicker.1.7.css'] = t('Date Popup default');
$paths[$jquery_ui_path .'/themes/base/ui.datepicker.css'] = t('jQuery UI default');
break;
}
// Populate options array with available themes from jQuery UI.
// Each theme must be placed in its own subdirectory of jquery.ui/themes/,
// e.g., jquery.ui/themes/cupertino, jquery.ui/themes/ui-lightness, etc.
// All themes can be downloaded from
// http://jquery-ui.googlecode.com/files/jquery-ui-themes-1.7.zip
foreach (scandir($jquery_ui_path .'/themes/') as $dir) {
$full_dir_path = $jquery_ui_path .'/themes/'. $dir;
if (is_dir($full_dir_path) && $dir != '.' && $dir != '..' && $dir != 'base') {
$paths[$full_dir_path .'/ui.datepicker.css'] = t('jQuery UI @theme', array('@theme' => $dir));
}
}
return $paths;
}
/**
* Implementation of hook_init().
*/
function date_popup_init() {
global $user;
if (!module_exists('jquery_ui') || !function_exists('jquery_ui_get_path')) {
if ($user->uid == 1) {
drupal_set_message(t('The Date Popup module now requires the <a href="@link">jQuery UI module</a> version 6.x-1.5 or higher as a source for the datepicker. Please install it immediately.', array('@link' => 'http://drupal.org/project/jquery_ui')), 'error');
}
return;
}
$date_popup_css_file = variable_get('date_popup_css_file', date_popup_css_default());
// Force loading base jquery-ui.css when using date_popup default theme,
// otherwise the popup calendar will be displayed with no theme at all.
if ($date_popup_css_file == date_popup_css_default()) {
drupal_add_css(jquery_ui_get_path() .'/themes/base/jquery-ui.css');
}
// Otherwise, load the CSS file in the corresponding theme directory.
else {
drupal_add_css(dirname($date_popup_css_file) .'/jquery-ui.css');
}
drupal_add_css($date_popup_css_file);
if (variable_get('date_popup_timepicker', 'default') == 'default') {
drupal_add_css(drupal_get_path('module', 'date_popup') .'/themes/jquery.timeentry.css');
}
}
/**
* Create a unique CSS id name and output a single inline JS block for
* each startup function to call and settings array to pass it. This
* used to create a unique CSS class for each unique combination of
* function and settings, but using classes requires a DOM traversal
* and is much slower than an id lookup. The new approach returns to
* requiring a duplicate copy of the settings/code for every element
* that uses them, but is much faster. We could combine the logic by
* putting the ids for each unique function/settings combo into
* Drupal.settings and searching for each listed id.
*
* @param $pfx
* The CSS class prefix to search the DOM for.
* TODO : unused ?
* @param $func
* The jQuery function to invoke on each DOM element containing the
* returned CSS class.
* @param $settings
* The settings array to pass to the jQuery function.
* @returns
* The CSS id to assign to the element that should have
* $func($settings) invoked on it.
*/
function date_popup_js_settings_id($id, $func, $settings) {
static $js_added = FALSE;
static $id_count = array();
// Make sure popup date selector grid is in correct year.
if (!empty($settings['yearRange'])) {
$parts = explode(':', $settings['yearRange']);
// Set the default date to 0 or the lowest bound if the date ranges do not include the current year
// Necessary for the datepicker to render and select dates correctly
$defaultDate = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0;
// The 1.7 version of datepicker renders the range of year options
// relative to the drawn year in the popup, and will re-render the options
// whenever the year changes.
if (strpos(jquery_ui_get_version(), '1.7') === 0 && ($parts[0] >= 0 || 0 >= $parts[1])) {
$range = max($parts) - min($parts);
$defaultDate = $parts[0];
$settings['yearRange'] = '-' . $range . ':' . '+' . $range;
}
$settings += array('defaultDate' => (string) $defaultDate . 'y');
}
if (!$js_added) {
drupal_add_js(drupal_get_path('module', 'date_popup') .'/date_popup.js');
$js_added = TRUE;
}
// We use a static array to account for possible multiple form_builder()
// calls in the same request (form instance on 'Preview').
if (!isset($id_count[$id])) {
$id_count[$id] = 0;
}
// It looks like we need the additional id_count for this to
// work correctly when there are multiple values.
// $return_id = "$id-$func-popup";
$return_id = "$id-$func-popup-". $id_count[$id]++;
$js_settings['datePopup'][$return_id] = array(
'func' => $func,
'settings' => $settings
);
drupal_add_js($js_settings, 'setting');
return $return_id;
}
function date_popup_theme() {
return array(
'date_popup' => array('arguments' => array('element' => NULL)),
);
}
/**
* Implementation of hook_elements().
*
* Set the #type to date_popup and fill the element #default_value with
* a date adjusted to the proper local timezone in datetime format (YYYY-MM-DD HH:MM:SS).
*
* The element will create two textfields, one for the date and one for the
* time. The date textfield will include a jQuery popup calendar date picker,
* and the time textfield uses a jQuery timepicker.
*
* NOTE - Converting a date stored in the database from UTC to the local zone
* and converting it back to UTC before storing it is not handled by this
* element and must be done in pre-form and post-form processing!!
*
* #date_timezone
* The local timezone to be used to create this date.
*
* #date_format
* Unlike earlier versions of this popup, most formats will work.
*
* #date_increment
* Increment minutes and seconds by this amount, default is 1.
*
* #date_year_range
* The number of years to go back and forward in a year selector,
* default is -3:+3 (3 back and 3 forward).
*
*/
function date_popup_elements() {
return array(
'date_popup' => array(
'#input' => TRUE,
'#tree' => TRUE,
'#date_timezone' => date_default_timezone_name(),
'#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'),
'#date_increment' => 1,
'#date_year_range' => '-3:+3',
'#process' => array('date_popup_process'),
),
);
}
/**
* Javascript popup element processing.
* Add popup attributes to $element.
*
* In regular FAPI processing $element['#value'] will contain a string
* value before the form is submitted, and an array during submission.
*
* In regular FAPI processing $edit is empty until the form is submitted
* when it will contain an array.
*
* Views widget processing now receives the same values as normal FAPI
* processing (that was not true in Views 1).
*
*/
function date_popup_process($element, $edit, $form_state, $form) {
date_popup_load();
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc');
$date = NULL;
$granularity = date_format_order($element['#date_format']);
if (!empty($edit) && is_array($edit) && !empty($edit['date'])) {
$datetime = date_popup_input_value($element);
$date = date_make_date($datetime, $element['#date_timezone'], DATE_DATETIME, $granularity);
}
elseif (!empty($element['#value'])) {
$date = date_make_date($element['#value'], $element['#date_timezone'], DATE_DATETIME, $granularity);
}
date_increment_round($date, $element['#date_increment']);
$granularity = date_format_order($element['#date_format']);
$element['#tree'] = TRUE;
$element['#granularity'] = $granularity;
$element['date'] = date_popup_process_date($element, $edit, $date);
$element['time'] = date_popup_process_time($element, $edit, $date);
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_popup_validate');
}
else {
$element['#element_validate'] = array('date_popup_validate');
}
return $element;
}
/**
* Process the date portion of the element.
*/
function date_popup_process_date(&$element, $edit = NULL, $date = NULL) {
$granularity = $element['#granularity'];
$date_granularity = array_intersect($granularity, array('month', 'day', 'year'));
$time_granularity = array_intersect($granularity, array('hour', 'minute', 'second'));
$date_format = (date_limit_format($element['#date_format'], $date_granularity));
if (empty($date_granularity)) return array();
// The datepicker can't handle zero or negative values like 0:+1
// even though the Date API can handle them, so rework the value
// we pass to the datepicker to use defaults it can accept (such as +0:+1)
// date_range_string() adds the necessary +/- signs to the range string.
$range = date_range_years($element['#date_year_range'], $date);
$year_range = date_range_string($range);
$settings = array(
'prevText' => '&laquo;',
'nextText' => '&raquo;',
'currentText' => date_t('Today', 'date_nav'),
'changeMonth' => TRUE,
'changeYear' => TRUE,
'clearText' => t('Clear'),
'closeText' => t('Close'),
'firstDay' => intval(variable_get('date_first_day', 1)),
'dayNames' => date_week_days(TRUE),
'dayNamesShort' => date_week_days_abbr(TRUE, TRUE, 3),
'dayNamesMin' => date_week_days_abbr(TRUE, TRUE, 2),
'monthNames' => array_values(date_month_names(TRUE)),
'monthNamesShort' => array_values(date_month_names_abbr(TRUE)),
//'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png",
//'buttonImageOnly' => TRUE,
'autoPopUp' => 'focus',
'closeAtTop' => FALSE,
'speed' => 'immediate',
'dateFormat' => date_popup_format_to_popup($date_format, 'datepicker'),
'yearRange' => $year_range,
// Custom setting, will be expanded in Drupal.behaviors.date_popup()
'fromTo' => isset($fromto),
);
// Create a unique id for each set of custom settings.
$id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
// Manually build this element and set the value - this will prevent corrupting
// the parent value
$parents = array_merge($element['#parents'], array('date'));
$sub_element = array(
'#type' => 'textfield',
'#default_value' => (!empty($element['#value']) || !empty($edit['date'])) && is_object($date) ? date_format_date($date, 'custom', $date_format) : '',
'#id' => $id,
'#input' => FALSE,
'#size' => !empty($element['#size']) ? $element['#size'] : 20,
'#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30,
'#attributes' => $element['#attributes'],
'#parents' => $parents,
'#name' => array_shift($parents) . '['. implode('][', $parents) .']'
);
$sub_element['#value'] = $sub_element['#default_value'];
// TODO, figure out exactly when we want this description. In many places it is not desired.
$sub_element['#description'] = ' '. t('Format: @date', array('@date' => date_format_date(date_now(), 'custom', $date_format)));
return $sub_element;
}
/**
* Process the time portion of the element.
*/
function date_popup_process_time(&$element, $edit = NULL, $date = NULL) {
$granularity = $element['#granularity'];
$time_granularity = array_intersect($granularity, array('hour', 'minute', 'second'));
$time_format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity));
if (empty($time_granularity)) return array();
$spinner_text = array(t('Now'), t('Previous field'), t('Next field'), t('Increment'), t('Decrement'));
$settings = array(
'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE,
'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE),
'timeSteps' => array(1, intval($element['#date_increment']), (in_array('second', $granularity) ? $element['#date_increment'] : 0)),
'spinnerImage' => '',
'fromTo' => isset($fromto),
);
// Create a unique id for each set of custom settings.
$id = date_popup_js_settings_id($element['#id'], 'timeEntry', $settings);
// Manually build this element and set the value - this will prevent corrupting
// the parent value
$parents = array_merge($element['#parents'], array('time'));
$sub_element = array(
'#type' => 'textfield',
'#default_value' => (!empty($element['#value']) || !empty($edit['time'])) && is_object($date) ? date_format_date($date, 'custom', $time_format) : '',
'#id' => $id,
'#input' => FALSE,
'#size' => 10,
'#maxlength' => 10,
'#parents' => $parents,
'#name' => array_shift($parents) . '['. implode('][', $parents) .']'
);
$sub_element['#value'] = $sub_element['#default_value'];
// TODO, figure out exactly when we want this description. In many places it is not desired.
$sub_element['#description'] = t('Format: @date', array('@date' => date_format_date(date_now(), 'custom', $time_format)));
return ($sub_element);
}
/**
* Massage the input values back into a single date.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*
*/
function date_popup_validate($element, &$form_state) {
if (is_string($element['#value'])) {
return;
}
$granularity = $element['#granularity'];
$date_granularity = array_intersect($granularity, array('month', 'day', 'year'));
$time_granularity = array_intersect($granularity, array('hour', 'minute', 'second'));
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
$label = t($label);
// If the field is empty and not required, set it to empty and return.
// If the field is empty and required, set error message and return.
$error_field = implode('][', $element['#parents']);
if (empty($element['#value']['date'])) {
if ($element['#required']) {
// Set message on both date and time to get them highlighted properly.
$message = t('Field %field is required.', array('%field' => $label));
if (!empty($date_granularity)) {
form_set_error($error_field .'][date', $message);
$message = ' ';
}
if (!empty($time_granularity)) {
form_set_error($error_field .'][time', $message);
}
}
form_set_value($element, NULL, $form_state);
return;
}
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc');
date_popup_load();
$value = date_popup_input_value($element);
// If the created date is valid, set it.
if (!empty($value)) {
form_set_value($element, $value, $form_state);
return;
}
else {
// Set message on both date and time to get them highlighted properly.
$message = t('Field %field is invalid.', array('%field' => $label));
if (!empty($date_granularity)) {
form_set_error($error_field .'][date', $message);
$message = ' ';
}
if (!empty($time_granularity)) {
form_set_error($error_field .'][time', $message);
}
}
form_set_value($element, NULL, $form_state);
}
/**
* Helper function for extracting a date value out of user input.
*
* @param autocomplete
* Should we add a time value to complete the date if there is no time?
* Useful anytime the time value is optional.
*/
function date_popup_input_value($element, $auto_complete = FALSE) {
date_popup_load();
$granularity = date_format_order($element['#date_format']);
$format = $element['#date_format'];
$format = strtr($format, timepicker_format_replacements());
$format = date_limit_format($format, $granularity);
// Evaluate date and time parts separately since we can't know or care
// how they're combined in the complete date format.
$time_format = date_limit_format($format, array('hour', 'minute', 'second'));
$date_format = date_limit_format($format, array('year', 'month', 'day'));
$value = '';
if (is_array($element['#value']) && !empty($element['#value']['date'])) {
$date = date_convert_from_custom(trim(!empty($element['#value']['date']) ? $element['#value']['date'] : ''), $date_format);
$time = date_convert_from_custom(trim(!empty($element['#value']['time']) ? $element['#value']['time'] : ''), $time_format);
$value = trim(drupal_substr($date, 0, 10) .' '. drupal_substr($time, 11, 8));
}
if (date_is_valid($value, DATE_DATETIME, $granularity)) {
$date = date_make_date($value, $element['#date_timezone'], DATE_DATETIME, $granularity);
$value = date_convert($date, DATE_OBJECT, DATE_DATETIME);
return $value;
}
return NULL;
}
/**
* Allowable time formats.
*/
function date_popup_time_formats($with_seconds = FALSE) {
return array(
'H:i:s',
'h:i:sA',
);
}
/**
* Format options array.
*
* There are just a few options available for the earlier 'calendar'
* version.
*/
function date_popup_formats() {
return array_keys(date_format_options());
}
/**
* Store personalized format options for each user.
*
* TODO see what is needed to remove this completely.
* It is now only used by Date Popup and not really needed there.
*
* @return array
*/
function date_format_options() {
global $user;
$options = array();
$formats = date_get_formats();
$options = array();
module_load_include('inc', 'date', 'date_admin');
$now = date_example_date();
if (!empty($now)) {
foreach ($formats as $type => $format_types) {
foreach ($format_types as $format => $format_attributes) {
// Create an option that shows date only without time, along with the
// default string which has both date and time.
$no_time = date_limit_format($format, array('month', 'day', 'year'));
$zones = array('', 'O', 'P', 'e');
foreach ($zones as $zone) {
$time_format = !empty($zone) ? $format .' '. $zone : $format;
$options[$no_time] = date_format_date($now, 'custom', $no_time);
$options[$time_format] = date_format_date($now, 'custom', $time_format);
}
}
}
asort($options);
}
return $options;
}
/**
* Recreate a date format string so it has the values popup expects.
*
* @param string $format
* a normal date format string, like Y-m-d
* @return string
* A format string in popup format, like YMD-, for the
* earlier 'calendar' version, or m/d/Y for the later 'datepicker'
* version.
*/
function date_popup_format_to_popup($format) {
if (empty($format)) {
$format = 'Y-m-d';
}
$replace = datepicker_format_replacements();
return strtr($format, $replace);
}
/**
* Recreate a date format string so it has the values popup expects.
*
* @param string $format
* a normal date format string, like Y-m-d
* @return string
* a format string in popup format, like YMD-
*/
function date_popup_format_to_popup_time($format) {
if (empty($format)) {
$format = 'H:i';
}
$format = strtr($format, timepicker_format_replacements());
$format = str_replace(array(' ', '/', '-', '.', ',', 'F', 'M', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format);
return $format;
}
/**
* Reconstruct popup format string into normal format string.
*
* @param string $format
* a string in popup format, like YMD-
* @return string
* a normal date format string, like Y-m-d
*/
function date_popup_popup_to_format($format) {
$replace = array_flip(datepicker_format_replacements());
return strtr($format, $replace);
}
function timepicker_format_replacements() {
return array(
'G' => 'H',
'g' => 'h',
'a' => 'A',
' a' => 'A',
' A' => 'A',
);
}
/**
* The format replacement patterns for the new datepicker.
*/
function datepicker_format_replacements() {
return array(
'd' => 'dd',
'j' => 'd',
'l' => 'DD',
'D' => 'D',
'm' => 'mm',
'n' => 'm',
'F' => 'MM',
'M' => 'M',
'Y' => 'yy',
'y' => 'y',
);
}
/**
* Format a date popup element.
*
* Use a class that will float date and time next to each other.
*/
function theme_date_popup($element) {
$output = '';
$class = 'container-inline-date form-item';
// Add #date_float to allow date parts to float together on the same line.
if (empty($element['#date_float'])) {
$class .= ' date-clear-block';
}
if (isset($element['#children'])) {
$output = $element['#children'];
}
return '<div class="'. $class .'">'. theme('form_element', $element, $output) .'</div>';
}
/**
* Implementation of hook_menu().
*/
function date_popup_menu() {
$items = array();
$items['admin/settings/date_popup'] = array(
'title' => 'Date Popup Configuration',
'description' => 'Allows the user to configure the Date Popup settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('date_popup_settings'),
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* General configuration form for controlling the Date Popup behaviour.
*/
function date_popup_settings() {
$form['date_popup_css_file'] = array(
'#type' => 'select',
'#title' => t('Datepicker css'),
'#description' => t('Choose the css to use for the jQuery UI datepicker.'),
'#options' => date_popup_css_options(),
'#default_value' => variable_get('date_popup_css_file', date_popup_css_default()),
);
$form['date_popup_css_file']['#prefix'] = t('<p>The Date Popup calendar datepicker uses the jQuery UI datepicker. You must install the jQuery UI module for this to work. It has its own css file, or there is a Drupal adaptation included in Date Popup that you can use.</p>');
$form['date_popup_timepicker'] = array(
'#type' => 'select',
'#options' => array('default' => t('Use default jQuery timepicker'), 'none' => t('Manual time entry, no jQuery timepicker')),
'#title' => t('Timepicker'),
'#default_value' => variable_get('date_popup_timepicker', 'default'),
'#description' => t("Choose the jQuery timepicker to user."),
);
$form['date_popup_timepicker']['#prefix'] .= t('<p>The Date Popup module uses a jQuery timepicker module. There is no "official" jQuery UI timepicker, and not everyone likes the one that is included here. If you do not want to use the timepicker, you can turn it off below and users will get a regular textfield instead.</p>');
$css = <<<EOM
/* ___________ IE6 IFRAME FIX ________ */
.ui-datepicker-cover {
display: none; /*sorry for IE5*/
display/**/: block; /*sorry for IE5*/
position: absolute; /*must have*/
z-index: -1; /*must have*/
filter: mask(); /*must have*/
top: -4px; /*must have*/
left: -4px; /*must have*/ /* LTR */
width: 200px; /*must have*/
height: 200px; /*must have*/
}
EOM;
$form['#suffix'] = t('<p>The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7 and IE 8, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:</p>') .'<blockquote><PRE>'. $css .'</PRE></blockquote>';
return system_settings_form($form);
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,166 @@
/* Main Style Sheet for jQuery Calendar */
#calendar_div, .calendar_inline {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
padding: 0;
margin: 0;
background: #fff;
}
#calendar_div {
display: none;
border: 1px solid #999;
z-index: 10; /*must have*/
}
#calendar_div, .calendar_control, .calendar_links, .calendar_header, .calendar {
width: 185px;
}
.calendar_inline {
float: left;
display: block;
border: 0;
}
.calendar_dialog {
padding: 5px !important;
border: 4px ridge #ddd !important;
}
button.calendar_trigger {
width: 25px;
}
img.calendar_trigger {
margin: 2px;
vertical-align: middle;
}
.calendar_prompt {
float: left;
width: 181px;
padding: 2px;
background: #ddd;
color: #000;
}
* html .calendar_prompt {
width: 185px;
}
.calendar_control, .calendar_links, .calendar_header, .calendar {
clear: both;
float: left;
color: #000;
}
.calendar_control, .calendar_header {
background: #eee;
}
.calendar_links {
background: #fff;
}
.calendar_control, .calendar_links {
font-size: 80%;
letter-spacing: 1px;
background: #eee;
}
.calendar_links label { /* disabled links */
padding: 2px 5px;
color: #666;
}
.calendar_clear, .calendar_prev {
float: left;
width: 35px;
}
.calendar_current {
float: left;
width: 80px;
text-align: center;
}
.calendar_close, .calendar_next {
float: right;
width: 35px;
}
.calendar_header {
background: #ddd;
text-align: center;
}
.calendar_header select {
background: #ddd;
color: #000;
border: 0px;
}
.calendar {
background: #fff;
text-align: center;
font-size: 100%;
}
.calendar a {
display: block;
width: 100%;
}
.calendar .calendar_titleRow {
background: #eee;
}
.calendar .calendar_daysRow {
background: #fff;
color: #666;
}
.calendar .calendar_daysCell {
color: #000;
border: 1px solid #fff;
}
#calendar .calendar_daysCell a{
display: block;
}
.calendar .calendar_weekEndCell {
background: #fff;
}
.calendar .calendar_daysCellOver {
background: #fff;
border: 1px solid #777;
}
.calendar .calendar_unselectable {
color: #333;
}
.calendar_today {
background: #eee !important;
}
.calendar_currentDay {
background: #ddd !important;
}
/* ________ CALENDAR LINKS _______
** Reset link properties and then override them with !important */
#calendar_div a, .calendar_inline a {
cursor: pointer;
margin: 0;
padding: 0;
background: none;
color: #000;
}
.calendar_inline .calendar_links a {
padding: 0 5px !important;
}
.calendar_control a, .calendar_links a {
padding: 2px 5px !important;
color: #333 !important;
}
.calendar_titleRow a {
color: #333 !important;
}
.calendar_control a:hover {
background: #eee !important;
color: #333 !important;
}
.calendar_links a:hover, .calendar_titleRow a:hover {
background: #eee !important;
color: #333 !important;
}
/* ___________ IE6 IFRAME FIX ________ */
.calendar_cover {
display: none; /*sorry for IE5*/
display/**/: block; /*sorry for IE5*/
position: absolute; /*must have*/
z-index: -1; /*must have*/
filter: mask(); /*must have*/
top: -4px; /*must have*/
left: -4px; /*must have*/
width: 193px; /*must have to match width and borders*/
height: 200px; /*must have to match maximum height*/
}

View file

@ -0,0 +1,58 @@
#ui-datepicker-div {
font-size: 100%;
font-family: Verdana, sans-serif;
background: #eee;
border-right:2px #666 solid;
border-bottom:2px #666 solid;
z-index: 9999;
}
/* Datepicker
----------------------------------*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
.ui-datepicker .ui-datepicker-prev { left:2px; }
.ui-datepicker .ui-datepicker-next { right:2px; }
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; }
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; }
/* RTL support */
.ui-datepicker-rtl { direction: rtl; }
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }

View file

@ -0,0 +1,137 @@
/* Smoothness Theme for jQuery UI Datepicker */
#ui-datepicker-div table,
#ui-datepicker-div td,
#ui-datepicker-div th {
margin: 0;
padding: 0;
}
#ui-datepicker-div,
#ui-datepicker-div table,
.ui-datepicker-div,
.ui-datepicker-div table,
.ui-datepicker-inline,
.ui-datepicker-inline table {
font-size: 12px !important;
}
.ui-datepicker-div, .ui-datepicker-inline, #ui-datepicker-div {
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
background: #ffffff;
border: 2px solid #d3d3d3;
font-family: Verdana, Arial, sans-serif;
font-size: 1.1em;
margin: 0;
padding: 2.5em .5em .5em .5em;
position: relative;
width: 15.5em;
}
#ui-datepicker-div {
background: #ffffff;
display: none;
z-index: 9999; /*must have*/
}
.ui-datepicker-inline {
display: block;
float: left; /* LTR */
}
.ui-datepicker-control {
display: none;
}
.ui-datepicker-current {
display: none;
}
.ui-datepicker-next, .ui-datepicker-prev {
background: #e6e6e6 url(images/e6e6e6_40x100_textures_02_glass_75.png) 0 50% repeat-x; /* LTR */
left: .5em; /* LTR */
position: absolute;
top: .5em;
}
.ui-datepicker-next {
left: 14.6em;
}
.ui-datepicker-next:hover, .ui-datepicker-prev:hover {
background: #dadada url(images/dadada_40x100_textures_02_glass_75.png) 0 50% repeat-x; /* LTR */
}
.ui-datepicker-next a, .ui-datepicker-prev a {
background: url(images/888888_7x7_arrow_left.gif) 50% 50% no-repeat; /* LTR */
border: 1px solid #d3d3d3;
cursor: pointer;
display: block;
font-size: 1em;
height: 1.4em;
text-indent: -999999px;
width: 1.3em;
}
.ui-datepicker-next a {
background: url(images/888888_7x7_arrow_right.gif) 50% 50% no-repeat; /* LTR */
}
.ui-datepicker-prev a:hover {
background: url(images/454545_7x7_arrow_left.gif) 50% 50% no-repeat; /* LTR */
}
.ui-datepicker-next a:hover {
background: url(images/454545_7x7_arrow_right.gif) 50% 50% no-repeat; /* LTR */
}
.ui-datepicker-prev a:active {
background: url(images/222222_7x7_arrow_left.gif) 50% 50% no-repeat; /* LTR */
}
.ui-datepicker-next a:active {
background: url(images/222222_7x7_arrow_right.gif) 50% 50% no-repeat; /* LTR */
}
.ui-datepicker-header select {
background: #e6e6e6;
border: 1px solid #d3d3d3;
color: #555555;
font-size: 1em;
line-height: 1.4em;
margin: 0 !important;
padding: 0 !important;
position: absolute;
top: .5em;
}
.ui-datepicker-header select.ui-datepicker-new-month {
left: 2.2em; /* LTR */
width: 7em;
}
.ui-datepicker-header select.ui-datepicker-new-year {
left: 9.4em; /* LTR */
width: 5em;
}
table.ui-datepicker {
text-align: right; /* LTR */
width: 15.5em;
}
table.ui-datepicker td a {
color: #555555;
display: block;
padding: .1em .3em .1em 0; /* LTR */
text-decoration: none;
}
table.ui-datepicker tbody {
border-top: none;
}
table.ui-datepicker tbody td a {
background: #e6e6e6 url(images/e6e6e6_40x100_textures_02_glass_75.png) 0 50% repeat-x; /* LTR */
border: 1px solid #ffffff;
cursor: pointer;
}
table.ui-datepicker tbody td a:hover {
background: #dadada url(images/dadada_40x100_textures_02_glass_75.png) 0 50% repeat-x; /* LTR */
border: 1px solid #999999;
color: #212121;
}
table.ui-datepicker tbody td a:active {
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x; /* LTR */
border: 1px solid #dddddd;
color: #222222;
}
table.ui-datepicker .ui-datepicker-title-row td {
/*border-bottom: 1px solid #d3d3d3;*/
color: #222222;
font-size: .9em;
padding: .3em 0;
text-align: center;
text-transform: uppercase;
}
table.ui-datepicker .ui-datepicker-title-row td a {
color: #222222;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View file

@ -0,0 +1,8 @@
/* TimeEntry styles */
.timeEntry_control {
vertical-align: middle;
margin-left: 2px;
}
* html .timeEntry_control { /* IE only */
margin-top: -4px;
}

View file

@ -0,0 +1,17 @@
/* timeEntry styles */
.timeEntry_control {
vertical-align: middle;
margin-left: 2px;
}
* html .timeEntry_control { /* IE only */
margin-top: -4px;
}
/* Make sure background colors from other styles don't obscure the current element highlight */
.timeEntry {
background-color:transparent !important;
}
/* This is to keep FF from offering a drop-down list of previous values. */
/* For some reason hiding the div where the image belongs keeps that from happening. */
.timeEntry_control {
display:none;
}

View file

@ -0,0 +1,11 @@
name = Date Repeat API
description = A Date Repeat API to calculate repeating dates and times from iCal rules.
dependencies[] = date_api
package = Date/Time
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Install file for Date Repeat.
*/
/**
* Implementation of hook_install().
*/
function date_repeat_install() {
// Make sure this module loads after date_api.
db_query("UPDATE {system} SET weight = 1 WHERE name = 'date_repeat'");
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s' OR widget_type='%s' OR widget_type='%s'", 'date_select_repeat', 'date_text_repeat', 'date_popup_repeat');
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_uninstall().
*/
function date_repeat_uninstall() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s' OR widget_type='%s' OR widget_type='%s'", 'date_select_repeat', 'date_text_repeat', 'date_popup_repeat');
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_enable().
*/
function date_repeat_enable() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=1 WHERE widget_type='%s' OR widget_type='%s' OR widget_type='%s'", 'date_select_repeat', 'date_text_repeat', 'date_popup_repeat');
content_clear_type_cache(TRUE);
}
}
/**
* Implementation of hook_disable().
*/
function date_repeat_disable() {
if (module_exists('content')) {
drupal_load('module', 'content');
if (!db_table_exists(content_instance_tablename())) {
return;
}
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_type='%s' OR widget_type='%s' OR widget_type='%s'", 'date_select_repeat', 'date_text_repeat', 'date_popup_repeat');
content_clear_type_cache(TRUE);
}
}

View file

@ -0,0 +1,306 @@
<?php
/**
* @file
*
* This module creates a form element that allows users to select
* repeat rules for a date, and reworks the result into an iCal
* RRULE string that can be stored in the database.
*
* The module also parses iCal RRULEs to create an array of dates
* that meet their criteria.
*
* Other modules can use this API to add self-validating form elements
* to their dates, and identify dates that meet the RRULE criteria.
*
*/
/**
* Implementation of hook_elements().
*/
function date_repeat_elements() {
$type['date_repeat_rrule'] = array(
'#input' => TRUE,
'#process' => array('date_repeat_rrule_process'),
'#element_validate' => array('date_repeat_rrule_validate'),
);
return $type;
}
/**
* Implementation of hook_menu.
*/
function date_repeat_menu() {
$items = array();
$items['date_repeat_get_exception_form_ajax'] = array(
'page callback' => 'date_repeat_get_exception_form_ajax',
'page arguments' => array(1, 2),
'file' => 'date_repeat_form.inc',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK
);
return $items;
}
function date_repeat_theme() {
return array(
'date_repeat' => array('arguments' => array('element' => NULL)),
'date_repeat_current_exceptions' => array('arguments' => array('element' => NULL)),
'date_repeat_current_additions' => array('arguments' => array('element' => NULL)),
);
}
/**
* Helper function for FREQ options.
*/
function FREQ_options() {
return array(
'NONE' => t('-- Period'),
'DAILY' => date_t('Days', 'datetime_plural'),
'WEEKLY' => date_t('Weeks', 'datetime_plural'),
'MONTHLY' => date_t('Months', 'datetime_plural'),
'YEARLY' => date_t('Years', 'datetime_plural'),
);
}
function INTERVAL_options() {
$options = array(
0 => t('-- Frequency'),
1 => date_t('Every', 'date_order'),
);
for ($i = 2; $i < 367; $i++) {
$options[$i] = t('Every @number', array('@number' => $i));
}
return $options;
}
/**
* Helper function for FREQ options.
*
* Translated and untranslated arrays of the iCal day of week names.
* We need the untranslated values for date_modify(), translated
* values when displayed to user.
*/
function date_repeat_dow_day_options($translated = TRUE) {
return array(
'SU' => $translated ? date_t('Sunday', 'day_name') : 'Sunday',
'MO' => $translated ? date_t('Monday', 'day_name') : 'Monday',
'TU' => $translated ? date_t('Tuesday', 'day_name') : 'Tuesday',
'WE' => $translated ? date_t('Wednesday', 'day_name') : 'Wednesday',
'TH' => $translated ? date_t('Thursday', 'day_name') : 'Thursday',
'FR' => $translated ? date_t('Friday', 'day_name') : 'Friday',
'SA' => $translated ? date_t('Saturday', 'day_name') : 'Saturday',
);
}
function date_repeat_dow_day_options_ordered($week_start) {
$unordered = date_repeat_dow_day_options(FALSE);
if (variable_get('date_first_day', 1) > 0) {
for ($i = 1; $i <= variable_get('date_first_day', 1); $i++) {
$last = array_shift($weekdays);
array_push($weekdays, $last);
}
}
return $weekdays;
}
/**
* Helper function for BYDAY options.
*/
function date_repeat_dow_count_options() {
return array('' => date_t('Every', 'date_order')) + date_order_translated();
}
/**
* Helper function for BYDAY options.
*
* Creates options like -1SU and 2TU
*/
function date_repeat_dow_options() {
$options = array();
foreach (date_repeat_dow_count_options() as $count_key => $count_value) {
foreach (date_repeat_dow_day_options() as $dow_key => $dow_value) {
$options[$count_key . $dow_key] = $count_value .' '. $dow_value;
}
}
return $options;
}
/**
* Translate a day of week position to the iCal day name.
*
* Used with date_format($date, 'w') or get_variable('date_first_day'),
* which return 0 for Sunday, 1 for Monday, etc.
*
* dow 2 becomes 'TU', dow 3 becomes 'WE', and so on.
*/
function date_repeat_dow2day($dow) {
$days_of_week = array_keys(date_repeat_dow_day_options(FALSE));
return $days_of_week[$dow];
}
/**
* Shift the array of iCal day names into the right order
* for a specific week start day.
*/
function date_repeat_days_ordered($week_start_day) {
$days = array_flip(array_keys(date_repeat_dow_day_options(FALSE)));
$start_position = $days[$week_start_day];
$keys = array_flip($days);
if ($start_position > 0) {
for ($i = 1; $i <= $start_position; $i++) {
$last = array_shift($keys);
array_push($keys, $last);
}
}
return $keys;
}
/**
* Build a description of an iCal rule.
*
* Constructs a human-readable description of the rule.
*/
function date_repeat_rrule_description($rrule, $format = 'D M d Y') {
// Empty or invalid value.
if (empty($rrule) || !strstr($rrule, 'RRULE')) {
return;
}
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
require_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_calc.inc');
// Make sure there will be an empty description for any unused parts.
$description = array(
'!interval' => '',
'!byday' => '',
'!bymonth' => '',
'!count' => '',
'!until' => '',
'!except' => '',
'!additional' => '',
'!week_starts_on' => '',
);
$parts = date_repeat_split_rrule($rrule);
$additions = $parts[2];
$exceptions = $parts[1];
$rrule = $parts[0];
$interval = INTERVAL_options();
switch ($rrule['FREQ']) {
case 'WEEKLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every week', 'every @count weeks') .' ';
break;
case 'MONTHLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every month', 'every @count months') .' ';
break;
case 'YEARLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every year', 'every @count years') .' ';
break;
default:
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every day', 'every @count days') .' ';
break;
}
if (!empty($rrule['BYDAY'])) {
$days = date_repeat_dow_day_options();
$counts = date_repeat_dow_count_options();
$results = array();
foreach ($rrule['BYDAY'] as $byday) {
$day = drupal_substr($byday, -2);
$count = intval(str_replace(' '. $day, '', $byday));
if ($count = intval(str_replace(' ' . $day, '', $byday))) {
$results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', array('!repeats_every_interval ' => '', '!date_order' => strtolower($counts[drupal_substr($byday, 0, 2)]), '!day_of_week' => $days[$day])));
}
else {
$results[] = trim(t('!repeats_every_interval every !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day])));
}
}
$description['!byday'] = implode(' '. t('and') .' ', $results);
}
if (!empty($rrule['BYMONTH'])) {
if (sizeof($rrule['BYMONTH']) < 12) {
$results = array();
$months = date_month_names();
foreach ($rrule['BYMONTH'] as $month) {
$results[] = $months[$month];
}
if (!empty($rrule['BYMONTHDAY'])) {
$description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', array('!repeats_every_interval ' => '', '!month_days' => implode(', ', $rrule['BYMONTHDAY']), '!month_names' => implode(', ', $results))));
}
else {
$description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', array('!repeats_every_interval ' => '', '!month_names' => implode(', ', $results))));
}
}
}
if ($rrule['INTERVAL'] < 1) {
$rrule['INTERVAL'] = 1;
}
if (!empty($rrule['COUNT'])) {
$description['!count'] = trim(t('!repeats_every_interval !count times', array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT'])));
}
if (!empty($rrule['UNTIL'])) {
$until = date_ical_date($rrule['UNTIL'], 'UTC');
date_timezone_set($until, date_default_timezone());
$description['!until'] = trim(t('!repeats_every_interval until !until_date', array('!repeats_every_interval ' => '', '!until_date' => date_format_date($until, 'custom', $format))));
}
if ($exceptions) {
$values = array();
foreach ($exceptions as $exception) {
$values[] = date_format_date(date_ical_date($exception), 'custom', $format);
}
$description['!except'] = trim(t('!repeats_every_interval except !except_dates', array('!repeats_every_interval ' => '', '!except_dates' => implode(', ', $values))));
}
if ($additions) {
$values = array();
foreach ($additions as $addition) {
$values[] = date_format_date(date_ical_date($addition), 'custom', $format);
}
$description['!additional'] = trim(t('Also includes !additional_dates.', array('!additional_dates' => implode(', ', $values))));
}
if (!empty($rrule['WKST'])) {
$day_names = date_repeat_dow_day_options();
$description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])])));
}
return t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description);
}
/**
* Parse an iCal rule into a parsed RRULE array and an EXDATE array.
*/
function date_repeat_split_rrule($rrule) {
$parts = explode("\n", str_replace("\r\n", "\n", $rrule));
$rrule = array();
$exceptions = array();
$additions = array();
foreach ($parts as $part) {
if (strstr($part, 'RRULE')) {
$RRULE = str_replace('RRULE:', '', $part);
$rrule = (array) date_ical_parse_rrule('RRULE:', $RRULE);
}
elseif (strstr($part, 'EXDATE')) {
$EXDATE = str_replace('EXDATE:', '', $part);
$exceptions = (array) date_ical_parse_exceptions('EXDATE:', $EXDATE);
unset($exceptions['DATA']);
}
elseif (strstr($part, 'RDATE')) {
$RDATE = str_replace('RDATE:', '', $part);
$additions = (array) date_ical_parse_exceptions('RDATE:', $RDATE);
unset($additions['DATA']);
}
}
return array($rrule, $exceptions, $additions);
}
/**
* Analyze a RRULE and return dates that match it.
*/
function date_repeat_calc($rrule, $start, $end, $exceptions = array(), $timezone = NULL, $additions = array()) {
require_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_calc.inc');
return _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additions);
}
/**
* Generate the repeat rule setting form.
*/
function date_repeat_rrule_process($element, $edit, $form_state, $form) {
require_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_form.inc');
return _date_repeat_rrule_process($element, $edit, $form_state, $form);
}

View file

@ -0,0 +1,581 @@
<?php
/**
* @file
* Code to compute the dates that match an iCal RRULE.
*
* Moved to a separate file since it is not used on most pages
* so the code is not parsed unless needed.
*
* Extensive simpletests have been created to test the RRULE calculation
* results against official examples from RFC 2445.
*
* These calculations are expensive and results should be stored or cached
* so the calculation code is not called more often than necessary.
*
* Currently implemented:
* INTERVAL, UNTIL, COUNT, EXDATE, RDATE, BYDAY, BYMONTHDAY, BYMONTH,
* YEARLY, MONTHLY, WEEKLY, DAILY
*
* Currently not implemented:
*
* BYYEARDAY, MINUTELY, HOURLY, SECONDLY, BYMINUTE, BYHOUR, BYSECOND
* These could be implemented in the future.
*
* BYSETPOS
* Seldom used anywhere, so no reason to complicated the code.
*/
/**
* Private implementation of date_repeat_calc().
*
* Compute dates that match the requested rule, within a specified date range.
*/
function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additions) {
module_load_include('inc', 'date_api', 'date_api_ical');
if (empty($timezone)) {
$timezone = date_default_timezone_name();
}
// Make sure the 'EXCEPTIONS' string isn't appended to the rule.
$parts = explode("\n", $rrule);
if (count($parts)) {
$rrule = $parts[0];
}
// Get the parsed array of rule values.
$rrule = date_ical_parse_rrule('RRULE:', $rrule);
// These default values indicate there is no RRULE here.
if ($rrule['FREQ'] == 'NONE' || (isset($rrule['INTERVAL']) && $rrule['INTERVAL'] == 0)) {
return array();
}
// Create a date object for the start and end dates.
$start_date = date_make_date($start, $timezone);
// Versions of PHP greater than PHP 5.3.5 require that we set an explicit time when
// using date_modify() or the time may not match the original value. Adding this
// modifier gives us the same results in both older and newer versions of PHP.
$modify_time = ' ' . $start_date->format('g:ia');
// If the rule has an UNTIL, see if that is earlier than the end date.
if (!empty($rrule['UNTIL'])) {
$end_date = date_make_date($end, $timezone);
$until_date = date_ical_date($rrule['UNTIL'], $timezone);
if (date_format($until_date, 'U') < date_format($end_date, 'U')) {
$end_date = $until_date;
}
}
// The only valid option for an empty end date is when we have a count.
elseif (empty($end)) {
if (!empty($rrule['COUNT'])) {
$end_date = NULL;
}
else {
return array();
}
}
else {
$end_date = date_make_date($end, $timezone);
}
// Get an integer value for the interval, if none given, '1' is implied.
if (empty($rrule['INTERVAL'])) {
$rrule['INTERVAL'] = 1;
}
$interval = max(1, $rrule['INTERVAL']);
$count = isset($rrule['COUNT']) ? $rrule['COUNT'] : NULL;
if (empty($rrule['FREQ'])) {
$rrule['FREQ'] = 'DAILY';
}
// Make sure DAILY frequency isn't used in places it won't work;
if (!empty($rrule['BYMONTHDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
$rrule['FREQ'] = 'MONTHLY';
}
elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) {
$rrule['FREQ'] = 'WEEKLY';
}
// Find the time period to jump forward between dates.
switch ($rrule['FREQ']) {
case 'DAILY':
$jump = $interval . ' days';
break;
case 'WEEKLY':
$jump = $interval . ' weeks';
break;
case 'MONTHLY':
$jump = $interval . ' months';
break;
case 'YEARLY':
$jump = $interval . ' years';
break;
}
$rrule = date_repeat_adjust_rrule($rrule, $start_date);
// The start date always goes into the results, whether or not it meets
// the rules. RFC 2445 includes examples where the start date DOES NOT
// meet the rules, but the expected results always include the start date.
$days = array(date_format($start_date, DATE_FORMAT_DATETIME));
// BYMONTHDAY will look for specific days of the month in one or more months.
// This process is only valid when frequency is monthly or yearly.
if (!empty($rrule['BYMONTHDAY'])) {
$finished = FALSE;
$current_day = drupal_clone($start_date);
$direction_days = array();
// Deconstruct the day in case it has a negative modifier.
foreach ($rrule['BYMONTHDAY'] as $day) {
preg_match("@(-)?([0-9]{1,2})@", $day, $regs);
if (!empty($regs[2])) {
// Convert parameters into full day name, count, and direction.
$direction_days[$day] = array(
'direction' => !empty($regs[1]) ? $regs[1] : '+',
'direction_count' => $regs[2],
);
}
}
while (!$finished) {
$period_finished = FALSE;
while (!$period_finished) {
foreach ($rrule['BYMONTHDAY'] as $monthday) {
$day = $direction_days[$monthday];
$current_day = date_repeat_set_month_day($current_day, NULL, $day['direction_count'], $day['direction'], $timezone, $modify_time);
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
if ($finished = date_repeat_is_finished($current_day, $days, $count, $end_date)) {
$period_finished = TRUE;
}
}
// If it's monthly, keep looping through months, one INTERVAL at a time.
if ($rrule['FREQ'] == 'MONTHLY') {
if ($finished = date_repeat_is_finished($current_day, $days, $count, $end_date)) {
$period_finished = TRUE;
}
// Back up to first of month and jump.
$current_day = date_repeat_set_month_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+' . $jump . $modify_time);
}
// If it's yearly, break out of the loop at the
// end of every year, and jump one INTERVAL in years.
else {
if (date_format($current_day, 'n') == 12) {
$period_finished = TRUE;
}
else {
// Back up to first of month and jump.
$current_day = date_repeat_set_month_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+1 month' . $modify_time);
}
}
}
if ($rrule['FREQ'] == 'YEARLY') {
// Back up to first of year and jump.
$current_day = date_repeat_set_year_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+' . $jump . $modify_time);
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
}
}
// This is the simple fallback case, not looking for any BYDAY,
// just repeating the start date. Because of imputed BYDAY above, this
// will only test TRUE for a DAILY or less frequency (like HOURLY).
elseif (empty($rrule['BYDAY'])) {
// $current_day will keep track of where we are in the calculation.
$current_day = drupal_clone($start_date);
$finished = FALSE;
$months = !empty($rrule['BYMONTH']) ? $rrule['BYMONTH'] : array();
while (!$finished) {
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
date_modify($current_day, '+' . $jump . $modify_time);
}
}
else {
// More complex searches for day names and criteria like '-1SU' or '2TU,2TH',
// require that we interate through the whole time period checking each BYDAY.
// Create helper array to pull day names out of iCal day strings.
$day_names = date_repeat_dow_day_options(FALSE);
$days_of_week = array_keys($day_names);
// Parse out information about the BYDAYs and separate them
// depending on whether they have directional parameters like -1SU or 2TH.
$month_days = array();
$week_days = array();
// Find the right first day of the week to use, iCal rules say Monday
// should be used if none is specified.
$week_start_rule = !empty($rrule['WKST']) ? trim($rrule['WKST']) : 'MO';
$week_start_day = $day_names[$week_start_rule];
// Make sure the week days array is sorted into week order,
// we use the $ordered_keys to get the right values into the key
// and force the array to that order. Needed later when we
// iterate through each week looking for days so we don't
// jump to the next week when we hit a day out of order.
$ordered = date_repeat_days_ordered($week_start_rule);
$ordered_keys = array_flip($ordered);
foreach ($rrule['BYDAY'] as $day) {
preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs);
if (!empty($regs[2])) {
// Convert parameters into full day name, count, and direction.
$direction_days[] = array(
'day' => $day_names[$regs[3]],
'direction' => !empty($regs[1]) ? $regs[1] : '+',
'direction_count' => $regs[2],
);
}
else {
$week_days[$ordered_keys[$regs[3]]] = $day_names[$regs[3]];
}
}
ksort($week_days);
// BYDAYs with parameters like -1SU (last Sun) or 2TH (second Thur)
// need to be processed one month or year at a time.
if (!empty($direction_days) && in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
$finished = FALSE;
$current_day = drupal_clone($start_date);
while (!$finished) {
foreach ($direction_days as $day) {
// Find the BYDAY date in the current month.
if ($rrule['FREQ'] == 'MONTHLY') {
$current_day = date_repeat_set_month_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time);
}
else {
$current_day = date_repeat_set_year_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time);
}
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
// Reset to beginning of period before jumping to next period.
// Needed especially when working with values like 'last Saturday'
// to be sure we don't skip months like February.
$year = date_format($current_day, 'Y');
$month = date_format($current_day, 'n');
if ($rrule['FREQ'] == 'MONTHLY') {
date_date_set($current_day, $year, $month, 1);
}
else {
date_date_set($current_day, $year, 1, 1);
}
// Jump to the next period.
date_modify($current_day, '+' . $jump . $modify_time);
}
}
// For BYDAYs without parameters,like TU,TH (every Tues and Thur),
// we look for every one of those days during the frequency period.
// Iterate through periods of a WEEK, MONTH, or YEAR, checking for
// the days of the week that match our criteria for each week in the
// period, then jumping ahead to the next week, month, or year,
// an INTERVAL at a time.
if (!empty($week_days) && in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) {
$finished = FALSE;
$current_day = drupal_clone($start_date);
$format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n';
$current_period = date_format($current_day, $format);
// Back up to the beginning of the week in case we are somewhere in the
// middle of the possible week days, needed so we don't prematurely
// jump to the next week. The date_repeat_add_dates() function will
// keep dates outside the range from getting added.
if (date_format($current_day, 'l') != $day_names[$day]) {
date_modify($current_day, '-1 ' . $week_start_day . $modify_time);
}
while (!$finished) {
$period_finished = FALSE;
while (!$period_finished) {
$moved = FALSE;
foreach ($week_days as $delta => $day) {
// Find the next occurence of each day in this week, only add it
// if we are still in the current month or year. The date_repeat_add_dates
// function is insufficient to test whether to include this date
// if we are using a rule like 'every other month', so we must
// explicitly test it here.
// If we're already on the right day, don't jump or we
// will prematurely move into the next week.
if (date_format($current_day, 'l') != $day) {
date_modify($current_day, '+1 ' . $day . $modify_time);
$moved = TRUE;
}
if ($rrule['FREQ'] == 'WEEKLY' || date_format($current_day, $format) == $current_period) {
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
}
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
// Make sure we don't get stuck in endless loop if the current
// day never got changed above.
if (!$moved) {
date_modify($current_day, '+1 day' . $modify_time);
}
// If this is a WEEKLY frequency, stop after each week,
// otherwise, stop when we've moved outside the current period.
// Jump to the end of the week, then test the period.
if ($finished || $rrule['FREQ'] == 'WEEKLY') {
$period_finished = TRUE;
}
elseif ($rrule['FREQ'] != 'WEEKLY' && date_format($current_day, $format) != $current_period) {
$period_finished = TRUE;
}
}
if ($finished) {
continue;
}
// We'll be at the end of a week, month, or year when
// we get to this point in the code.
// Go back to the beginning of this period before we jump, to
// ensure we jump to the first day of the next period.
switch ($rrule['FREQ']) {
case 'WEEKLY':
date_modify($current_day, '+1 ' . $week_start_day . $modify_time);
date_modify($current_day, '-1 week' . $modify_time);
break;
case 'MONTHLY':
date_modify($current_day, '-' . (date_format($current_day, 'j') - 1) . ' days' . $modify_time);
date_modify($current_day, '-1 month' . $modify_time);
break;
case 'YEARLY':
date_modify($current_day, '-' . date_format($current_day, 'z') . ' days' . $modify_time);
date_modify($current_day, '-1 year' . $modify_time);
break;
}
// Jump ahead to the next period to be evaluated.
date_modify($current_day, '+' . $jump . $modify_time);
$current_period = date_format($current_day, $format);
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
}
}
}
// add additional dates
foreach ($additions as $addition) {
$date = date_make_date($addition . ' ' . date_format($start_date, 'H:i:s'), $timezone);
$days[] = date_format($date, DATE_FORMAT_DATETIME);
}
sort($days);
return $days;
}
/**
* See if the RRULE needs some imputed values added to it.
*/
function date_repeat_adjust_rrule($rrule, $start_date) {
// If this is not a valid value, do nothing;
if (empty($rrule) || empty($rrule['FREQ'])) {
return array();
}
// RFC 2445 says if no day or monthday is specified when creating repeats for
// weeks, months, or years, impute the value from the start date.
if (empty($rrule['BYDAY']) && $rrule['FREQ'] == 'WEEKLY') {
$rrule['BYDAY'] = array(date_repeat_dow2day(date_format($start_date, 'w')));
}
elseif (empty($rrule['BYDAY']) && empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] == 'MONTHLY') {
$rrule['BYMONTHDAY'] = array(date_format($start_date, 'j'));
}
elseif (empty($rrule['BYDAY']) && empty($rrule['BYMONTHDAY']) && empty($rrule['BYYEARDAY']) && $rrule['FREQ'] == 'YEARLY') {
$rrule['BYMONTHDAY'] = array(date_format($start_date, 'j'));
if (empty($rrule['BYMONTH'])) {
$rrule['BYMONTH'] = array(date_format($start_date, 'n'));
}
}
// If we are processing rules for period other than YEARLY or MONTHLY
// and have BYDAYS like 2SU or -1SA, simplify them to SU or SA since the
// position rules make no sense in other periods and just add complexity.
elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
foreach ($rrule['BYDAY'] as $delta => $BYDAY) {
$rrule['BYDAY'][$delta] = drupal_substr($BYDAY, -2);
}
}
return $rrule;
}
/**
* Helper function to add found date to the $dates array.
*
* Check that the date to be added is between the start and end date
* and that it is not in the $exceptions, nor already in the $days array,
* and that it meets other criteria in the RRULE.
*/
function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $exceptions, $rrule) {
if (isset($rrule['COUNT']) && sizeof($days) >= $rrule['COUNT']) {
return FALSE;
}
$formatted = date_format($current_day, DATE_FORMAT_DATETIME);
if (!empty($end_date) && $formatted > date_format($end_date, DATE_FORMAT_DATETIME)) {
return FALSE;
}
if ($formatted < date_format($start_date, DATE_FORMAT_DATETIME)) {
return FALSE;
}
if (in_array(date_format($current_day, 'Y-m-d'), $exceptions)) {
return FALSE;
}
if (!empty($rrule['BYDAY'])) {
$BYDAYS = $rrule['BYDAY'];
foreach ($BYDAYS as $delta => $BYDAY) {
$BYDAYS[$delta] = drupal_substr($BYDAY, -2);
}
if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $BYDAYS)) {
return FALSE;
}}
if (!empty($rrule['BYYEAR']) && !in_array(date_format($current_day, 'Y'), $rrule['BYYEAR'])) {
return FALSE;
}
if (!empty($rrule['BYMONTH']) && !in_array(date_format($current_day, 'n'), $rrule['BYMONTH'])) {
return FALSE;
}
if (!empty($rrule['BYMONTHDAY'])) {
// Test month days, but only if there are no negative numbers.
$test = TRUE;
$BYMONTHDAYS = array();
foreach ($rrule['BYMONTHDAY'] as $day) {
if ($day > 0) {
$BYMONTHDAYS[] = $day;
}
else {
$test = FALSE;
break;
}
}
if ($test && !empty($BYMONTHDAYS) && !in_array(date_format($current_day, 'j'), $BYMONTHDAYS)) {
return FALSE;
}
}
// Don't add a day if it is already saved so we don't throw the count off.
if (in_array($formatted, $days)) {
return TRUE;
}
else {
$days[] = $formatted;
}
}
/**
* Stop when $current_day is greater than $end_date or $count is reached.
*/
function date_repeat_is_finished($current_day, $days, $count, $end_date) {
if (($count && sizeof($days) >= $count)
|| (!empty($end_date) && date_format($current_day, 'U') > date_format($end_date, 'U'))) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Set a date object to a specific day of the month.
*
* Example,
* date_set_month_day($date, 'Sunday', 2, '-')
* will reset $date to the second to last Sunday in the month.
* If $day is empty, will set to the number of days from the
* beginning or end of the month.
*/
function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) {
if (is_object($date_in)) {
$current_month = date_format($date_in, 'n');
// Reset to the start of the month.
// We should be able to do this with date_date_set(), but
// for some reason the date occasionally gets confused if run
// through this function multiple times. It seems to work
// reliably if we create a new object each time.
$datetime = date_format($date_in, DATE_FORMAT_DATETIME);
$datetime = substr_replace($datetime, '01', 8, 2);
$date = date_make_date($datetime, $timezone);
if ($direction == '-') {
// For negative search, start from the end of the month.
date_modify($date, '+1 month' . $modify_time);
}
else {
// For positive search, back up one day to get outside the
// current month, so we can catch the first of the month.
date_modify($date, '-1 day' . $modify_time);
}
if (empty($day)) {
date_modify($date, $direction . $count . ' days' . $modify_time);
}
else {
// Use the English text for order, like First Sunday
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
$order = date_order();
$step = $count <= 5 ? $order[$direction . $count] : $count;
date_modify($date, $step . ' ' . $day . $modify_time);
}
// If that takes us outside the current month, don't go there.
if (date_format($date, 'n') == $current_month) {
return $date;
}
}
return $date_in;
}
/**
* Set a date object to a specific day of the year.
*
* Example,
* date_set_year_day($date, 'Sunday', 2, '-')
* will reset $date to the second to last Sunday in the year.
* If $day is empty, will set to the number of days from the
* beginning or end of the year.
*/
function date_repeat_set_year_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) {
if (is_object($date_in)) {
$current_year = date_format($date_in, 'Y');
// Reset to the start of the month.
// See note above.
$datetime = date_format($date_in, DATE_FORMAT_DATETIME);
$datetime = substr_replace($datetime, '01-01', 5, 5);
$date = date_make_date($datetime, $timezone);
if ($direction == '-') {
// For negative search, start from the end of the year.
date_modify($date, '+1 year' . $modify_time);
}
else {
// For positive search, back up one day to get outside the
// current year, so we can catch the first of the year.
date_modify($date, '-1 day' . $modify_time);
}
if (empty($day)) {
date_modify($date, $direction . $count . ' days' . $modify_time);
}
else {
// Use the English text for order, like First Sunday
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
$order = date_order();
$step = $count <= 5 ? $order[$direction . $count] : $count;
date_modify($date, $step . ' ' . $day . $modify_time);
}
// If that takes us outside the current year, don't go there.
if (date_format($date, 'Y') == $current_year) {
return $date;
}
}
return $date_in;
}

View file

@ -0,0 +1,407 @@
<?php
/**
* @file
* Code to add a date repeat selection form to a date field and create
* an iCal RRULE from the chosen selections.
*
* Moved to a separate file since it is not used on most pages
* so the code is not parsed unless needed.
*
* Currently implemented:
* INTERVAL, UNTIL, EXDATE, RDATE, BYDAY, BYMONTHDAY, BYMONTH,
* YEARLY, MONTHLY, WEEKLY, DAILY
*
* Currently not implemented:
*
* BYYEARDAY, MINUTELY, HOURLY, SECONDLY, BYMINUTE, BYHOUR, BYSECOND
* These could be implemented in the future.
*
* COUNT
* The goal of this module is to create a way we can parse an iCal
* RRULE and pull out just dates for a specified date range, for
* instance with a date that repeats daily for several years, we might
* want to only be able to pull out the dates for the current year.
*
* Adding COUNT to the rules we create makes it impossible to do that
* without parsing and computing the whole range of dates that the rule
* will create. COUNT is left off of the user form completely for this
* reason.
*
* BYSETPOS
* Seldom used anywhere, so no reason to complicated the code.
*/
/**
* Generate the repeat setting form.
*/
function _date_repeat_rrule_process($element, $edit, $form_state, $form) {
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
if (empty($element['#date_repeat_widget'])) {
$element['#date_repeat_widget'] = module_exists('date_popup') ? 'date_popup' : 'date_select';
}
if (is_array($element['#value'])) {
$element['#value'] = date_repeat_merge($element['#value'], $element);
$rrule = date_api_ical_build_rrule($element['#value']);
}
else {
$rrule = $element['#value'];
}
// Empty the original string value of the RRULE so we can create
// an array of values for the form from the RRULE's contents.
$element['#value'] = '';
$parts = date_repeat_split_rrule($rrule);
$rrule = $parts[0];
$exceptions = $parts[1];
$additions = $parts[2];
$timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone_name();
$merged_values = date_repeat_merge($rrule, $element);
$UNTIL = '';
if (!empty($merged_values['UNTIL']['datetime'])) {
$until_date = date_make_date($merged_values['UNTIL']['datetime'], $merged_values['UNTIL']['tz']);
date_timezone_set($until_date, timezone_open($timezone));
$UNTIL = date_format($until_date, DATE_FORMAT_DATETIME);
}
$parent_collapsed = !empty($rrule['INTERVAL']) || !empty($rrule['FREQ']) ? 0 : (!empty($element['#date_repeat_collapsed']) ? $element['#date_repeat_collapsed'] : 0);
$element['#type'] = 'fieldset';
$element['#title'] = t('Repeat');
$element['#description'] = theme('advanced_help_topic', 'date_api', 'date-repeat-form') . t('Choose a frequency and period to repeat this date. If nothing is selected, the date will not repeat.');
$element['#collapsible'] = TRUE;
$element['#collapsed'] = $parent_collapsed;
// Make sure we don't get floating parts where we don't want them.
$element['#prefix'] = '<div class="date-clear">';
$element['#suffix'] = '</div>';
$element['INTERVAL'] = array(
'#type' => 'select',
//'#title' => t('Interval'),
'#default_value' => (!empty($rrule['INTERVAL']) ? $rrule['INTERVAL'] : 0),
'#options' => INTERVAL_options(),
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['FREQ'] = array(
'#type' => 'select',
//'#title' => t('Frequency'),
'#default_value' => !empty($rrule['FREQ']) ? $rrule['FREQ'] : 'NONE',
'#options' => FREQ_options(),
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['UNTIL'] = array(
'#tree' => TRUE,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'datetime' => array(
'#type' => $element['#date_repeat_widget'],
'#title' => t('Until'),
'#description' => t('Date to stop repeating this item.'),
'#default_value' => $UNTIL,
'#date_timezone' => $timezone,
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => isset($merged_values['UNTIL']['all_day']) ? $merged_values['UNTIL']['all_day'] : 1),
'granularity' => array('#type' => 'hidden', '#value' => isset($merged_values['UNTIL']['granularity']) ? $merged_values['UNTIL']['granularity'] : serialize(array('year', 'month', 'day'))),
);
$collapsed = TRUE;
if (!empty($merged_values['BYDAY']) || !empty($merged_values['BYMONTH'])) {
$collapsed = FALSE;
}
// start the advanced fieldset
$element['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced'),
'#collapsible' => TRUE,
'#collapsed' => $collapsed,
'#description' => t("If no advanced options are selected, the date will repeat on the day of week of the start date for weekly repeats, otherwise on the month and day of the start date. Use the options below to override that behavior to select specific months and days to repeat on. Use the 'Except' box to input dates that should be omitted from the results.") .' ',
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
);
$element['advanced']['BYMONTH'] = array(
'#type' => 'select',
'#title' => date_t('Month', 'datetime'),
'#default_value' => !empty($rrule['BYMONTH']) ? $rrule['BYMONTH'] : '',
'#options' => array('' => t('-- Any')) + date_month_names(TRUE),
'#multiple' => TRUE,
'#size' => 10,
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['advanced']['BYMONTHDAY'] = array(
'#type' => 'select',
'#title' => t('Day of Month'),
'#default_value' => !empty($rrule['BYMONTHDAY']) ? $rrule['BYMONTHDAY'] : '',
'#options' => array('' => t('-- Any')) + drupal_map_assoc(range(1, 31)) + drupal_map_assoc(range(-1, -31)),
'#multiple' => TRUE,
'#size' => 10,
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['advanced']['BYDAY'] = array(
'#type' => 'select',
'#title' => t('Day of Week'),
'#default_value' => !empty($rrule['BYDAY']) ? $rrule['BYDAY'] : '',
'#options' => array('' => t('-- Any')) + date_repeat_dow_options(),
//'#attributes' => array('size' => '5'),
'#multiple' => TRUE,
'#size' => 10,
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['exceptions'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => empty($exceptions) ? TRUE : FALSE,
'#title' => t('Except'),
'#description' => t('Dates to omit from the list of repeating dates.'),
'#prefix' => '<div id="date-repeat-exceptions" class="date-repeat">',
'#suffix' => '</div>',
);
$max = !empty($exceptions) ? sizeof($exceptions) : 0;
for ($i = 0; $i <= $max; $i++) {
$EXCEPT = '';
if (!empty($exceptions[$i]['datetime'])) {
$EXCEPT = $exceptions[$i]['datetime'];
}
$element['exceptions']['EXDATE'][$i] = array(
'#tree' => TRUE,
'datetime' => array(
'#type' => $element['#date_repeat_widget'],
'#default_value' => $EXCEPT,
'#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone_name(),
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => 1),
'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))),
);
}
// collect additions in the same way as exceptions - implements RDATE.
$element['additions'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => empty($additions) ? TRUE : FALSE,
'#title' => t('Additional'),
'#description' => t('Dates to add to the list of repeating dates.'),
'#prefix' => '<div id="date-repeat-additions" class="date-repeat">',
'#suffix' => '</div>',
);
$max = !empty($additions) ? sizeof($additions) : 0;
for ($i = 0; $i <= $max; $i++) {
$RDATE = '';
if (!empty($additions[$i]['datetime'])) {
$RDATE = $additions[$i]['datetime'];
}
$element['additions']['RDATE'][$i] = array(
'#tree' => TRUE,
'datetime' => array(
'#type' => $element['#date_repeat_widget'],
'#default_value' => $RDATE,
'#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone_name(),
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => 1),
'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))),
);
}
// Create an "Add another" button for the exceptions.
$field_name = $element['#parents'][0];
$element['exceptions']['exceptions_addmore'] = array(
'#type' => 'button',
'#value' => t('Add more exceptions'),
'#ahah' => array(
'event' => 'click',
'path' => 'date_repeat_get_exception_form_ajax/exceptions/' . $field_name,
'wrapper' => 'date-repeat-exceptions',
'method' => 'replace',
'effect' => 'fade'
)
);
// Create an "Add another" button for the additions.
$field_name = $element['#parents'][0];
$element['additions']['additions_addmore'] = array(
'#type' => 'button',
'#value' => t('Add more additions'),
'#ahah' => array(
'event' => 'click',
'path' => 'date_repeat_get_exception_form_ajax/additions/' . $field_name,
'wrapper' => 'date-repeat-additions',
'method' => 'replace',
'effect' => 'fade'
)
);
return $element;
}
/**
* Ajax callback to get the exceptions form. This is needed to
* implement the "Add another" button for the date repeat exceptions.
*
* @param $type - 'exceptions' or 'additions'.
* @param $field_name - The name of the date field.
*/
function date_repeat_get_exception_form_ajax($type, $field_name) {
// Get the cached form.
$form_state = array('storage' => NULL, 'submitted' => FALSE);
if (!($form = form_get_cache($_POST['form_build_id'], $form_state))) {
drupal_json(array('status' => FALSE, 'data' => ''));
exit();
}
// Set the form state values.
$form_state = array_merge($form_state, array('values' => $_POST));
// Set the new form rrule value.
$form[$field_name]['rrule']['#value'] = $form_state['values'][$field_name]['rrule'];
// Cache the new form state.
form_set_cache($_POST['form_build_id'], $form, $form_state);
// Rebuild the form.
$form_state = array();
$form['#post'] = array();
$form = form_builder($form['form_id']['#value'] , $form, $form_state);
// Force a rebuild of the Drupal.settings javascript object.
// - Borrowed from content.node_form.inc, content_add_more_js function
$javascript = drupal_add_js(NULL, NULL);
$output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
// Create our output.
$output = drupal_render($form[$field_name]['rrule'][$type]) . $output_js;
// Set the new exceptions form.
drupal_json(array('status' => TRUE, 'data' => $output));
exit();
}
/**
* Regroup values back into a consistant array, no matter what state it is in.
*/
function date_repeat_merge($form_values, $element) {
if (empty($form_values) || !is_array($form_values)) {
return $form_values;
}
if (array_key_exists('advanced', $form_values) || array_key_exists('exceptions', $form_values) || array_key_exists('additions', $form_values)) {
if (!array_key_exists('advanced', $form_values)) $form_values['advanced'] = array();
if (!array_key_exists('exceptions', $form_values)) $form_values['exceptions'] = array();
if (!array_key_exists('additions', $form_values)) $form_values['additions'] = array();
$form_values = array_merge($form_values, (array) $form_values['advanced'], (array) $form_values['exceptions'], (array) $form_values['additions']);
unset($form_values['advanced']);
unset($form_values['exceptions']);
unset($form_values['additions']);
}
if (array_key_exists('BYDAY', $form_values)) unset($form_values['BYDAY']['']);
if (array_key_exists('BYMONTH', $form_values)) unset($form_values['BYMONTH']['']);
if (array_key_exists('BYMONTHDAY', $form_values)) unset($form_values['BYMONTHDAY']['']);
if (array_key_exists('UNTIL', $form_values) && is_array($form_values['UNTIL']['datetime'])) {
$function = $element['#date_repeat_widget'] .'_input_value';
$until_element = $element;
$until_element['#value'] = $form_values['UNTIL']['datetime'];
$until_element['#date_format'] = date_limit_format($element['#date_format'], array('year', 'month', 'day'));
$form_values['UNTIL']['datetime'] = $function($until_element, 0, 10);
$form_values['UNTIL']['datetime'] = substr($form_values['UNTIL']['datetime'], 0, 10);
$form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour')));
$form_values['UNTIL']['all_day'] = TRUE;
}
if (array_key_exists('EXDATE', $form_values) && is_array($form_values['EXDATE'])) {
$function = $element['#date_repeat_widget'] .'_input_value';
$exdate_element = $element;
foreach ($form_values['EXDATE'] as $delta => $value) {
if (is_array($value['datetime'])) {
$exdate_element['#value'] = $form_values['EXDATE'][$delta]['datetime'];
$exdate_element['#date_format'] = date_limit_format($element['#date_format'], array('year', 'month', 'day'));
$form_values['EXDATE'][$delta]['datetime'] = $function($exdate_element);
}
}
}
if (array_key_exists('RDATE', $form_values) && is_array($form_values['RDATE'])) {
$function = $element['#date_repeat_widget'] .'_input_value';
$rdate_element = $element;
foreach ($form_values['RDATE'] as $delta => $value) {
if (is_array($value['datetime'])) {
$rdate_element['#value'] = $form_values['RDATE'][$delta]['datetime'];
$rdate_element['#date_format'] = date_limit_format($element['#date_format'], array('year', 'month', 'day'));
$form_values['RDATE'][$delta]['datetime'] = $function($rdate_element);
}
}
}
return $form_values;
}
/**
* Build a RRULE out of the form values.
*/
function date_repeat_rrule_validate($element, &$form_state) {
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
$form_values = $form_state['values'];
$field_name = $element['#parents'][0];
$item = $form_values[$field_name]['rrule'];
$item = date_repeat_merge($item, $element);
if (!empty($item['UNTIL']['datetime'])) {
$date = date_make_date($item['UNTIL']['datetime'], $item['UNTIL']['tz']);
date_time_set($date, 23, 59, 59);
date_timezone_set($date, timezone_open('UTC'));
$item['UNTIL']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
$item['UNTIL']['tz'] = 'UTC';
}
$rrule = date_api_ical_build_rrule($item);
form_set_value($element, $rrule, $form_state);
}
/**
* Theme the exception list as a table so the buttons line up
*/
function theme_date_repeat_current_exceptions($rows = array()) {
$rows_info = array();
foreach ($rows as $key => $value) {
if (drupal_substr($key, 0, 1) != '#') {
$rows_info[] = array(drupal_render($value['action']), drupal_render($value['display']));
}
}
return theme('table', array(t('Delete'), t('Current exceptions')), $rows_info);
}
/**
* Theme the exception list as a table so the buttons line up
*/
function theme_date_repeat_current_additions($rows = array()) {
$rows_info = array();
foreach ($rows as $key => $value) {
if (drupal_substr($key, 0, 1) != '#') {
$rows_info[] = array(drupal_render($value['action']), drupal_render($value['display']));
}
}
return theme('table', array(t('Delete'), t('Current additions')), $rows_info);
}
/**
* Themes the date repeat element.
*/
function theme_date_repeat($element) {
return theme('form_element', $element, '<div class="container-inline">'. drupal_render($element) . '</div>');
}

View file

@ -0,0 +1,12 @@
name = Date Timezone
description = Needed when using Date API. Overrides site and user timezone handling to set timezone names instead of offsets.
package = Date/Time
dependencies[] = date_api
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,606 @@
<?php
/**
* @file
* Install file for Date Timezone.
*/
function date_timezone_install() {
$ret = array();
module_enable(array('date_api'));
if (version_compare(PHP_VERSION, '5.2', '<')) {
module_enable(array('date_php4'));
}
return $ret;
}
function date_timezone_enable() {
module_enable(array('date_api'));
if (version_compare(PHP_VERSION, '5.2', '<')) {
module_enable(array('date_php4'));
}
}
function date_timezone_module_uninstall() {
$ret = array();
variable_del('date_default_timezone_name');
return $ret;
}
/**
* @file
* Installation file for Date Timezone
*/
/**
* Implementation of hook_requirements().
* Make sure a site timezone name has been selected.
*/
function date_timezone_requirements($phase) {
$requirements = array();
$t = get_t();
$error = FALSE;
$value = array();
switch ($phase) {
case 'runtime':
$tz_name = variable_get('date_default_timezone_name', NULL);
if ($tz_name === NULL) {
$error = TRUE;
$severity = REQUIREMENT_ERROR;
$value = $t('The Date Timezone module requires you to !link.', array('!link' => l($t('set the site timezone name'), 'admin/settings/date-time')));
}
elseif (function_exists('date_create')) {
$date = date_make_date('now', $tz_name);
if (date_offset_get($date) != variable_get('date_default_timezone', 0)) {
$error = TRUE;
$value = $t('The !link may not be correct.', array('!link' => l($t('site timezone name'), 'admin/settings/date-time')));
$severity = REQUIREMENT_WARNING;
}
}
}
if ($error) {
$requirements['date_default_timezone'] = array(
'title' => $t('Date Timezone requirements'),
'value' => $value,
'severity' => $severity,
);
}
return $requirements;
}
/**
* Get rid of deprecated timezone names.
*/
function date_timezone_update_5200() {
$ret = array();
$old = variable_get('date_default_timezone_name', NULL);
if (!empty($old)) {
$new = _date_timezone_replacement($old);
if (!empty($new) && $new != $old) {
variable_set('date_default_timezone_name', $new);
}
}
$results = db_query("SELECT DISTINCT timezone_name FROM {users} ORDER BY timezone_name");
while ($row = db_fetch_object($results)) {
if (!empty($row->timezone_name)) {
$new = _date_timezone_replacement($row->timezone_name);
if (!empty($new) && $new != $row->timezone_name) {
db_query("UPDATE {users} SET timezone_name = '%s' WHERE timezone_name = '%s'", $new, $row->timezone_name);
}
}
}
// Make sure the timezone name list gets updated.
cache_clear_all('date_timezone_identifiers_list', 'cache');
return $ret;
}
/**
* Create replacement values for deprecated timezone names.
*/
function _date_timezone_replacement($old) {
$replace = array(
'Brazil/Acre' => 'America/Rio_Branco',
'Brazil/DeNoronha' => 'America/Noronha',
'Brazil/East' => 'America/Recife',
'Brazil/West' => 'America/Manaus',
'Canada/Atlantic' => 'America/Halifax',
'Canada/Central' => 'America/Winnipeg',
'Canada/East-Saskatchewan' => 'America/Regina',
'Canada/Eastern' => 'America/Toronto',
'Canada/Mountain' => 'America/Edmonton',
'Canada/Newfoundland' => 'America/St_Johns',
'Canada/Pacific' => 'America/Vancouver',
'Canada/Saskatchewan' => 'America/Regina',
'Canada/Yukon' => 'America/Whitehorse',
'CET' => 'Europe/Berlin',
'Chile/Continental' => 'America/Santiago',
'Chile/EasterIsland' => 'Pacific/Easter',
'CST6CDT' => 'America/Chicago',
'Cuba' => 'America/Havana',
'EET' => 'Europe/Bucharest',
'Egypt' => 'Africa/Cairo',
'Eire' => 'Europe/Belfast',
'EST' => 'America/New_York',
'EST5EDT' => 'America/New_York',
'GB' => 'Europe/London',
'GB-Eire' => 'Europe/Belfast',
'Etc/GMT' => 'UTC',
'Etc/GMT+0' => 'UTC',
'Etc/GMT+1' => 'UTC',
'Etc/GMT+10' => 'UTC',
'Etc/GMT+11' => 'UTC',
'Etc/GMT+12' => 'UTC',
'Etc/GMT+2' => 'UTC',
'Etc/GMT+3' => 'UTC',
'Etc/GMT+4' => 'UTC',
'Etc/GMT+5' => 'UTC',
'Etc/GMT+6' => 'UTC',
'Etc/GMT+7' => 'UTC',
'Etc/GMT+8' => 'UTC',
'Etc/GMT+9' => 'UTC',
'Etc/GMT-0' => 'UTC',
'Etc/GMT-1' => 'UTC',
'Etc/GMT-10' => 'UTC',
'Etc/GMT-11' => 'UTC',
'Etc/GMT-12' => 'UTC',
'Etc/GMT-13' => 'UTC',
'Etc/GMT-14' => 'UTC',
'Etc/GMT-2' => 'UTC',
'Etc/GMT-3' => 'UTC',
'Etc/GMT-4' => 'UTC',
'Etc/GMT-5' => 'UTC',
'Etc/GMT-6' => 'UTC',
'Etc/GMT-7' => 'UTC',
'Etc/GMT-8' => 'UTC',
'Etc/GMT-9' => 'UTC',
'Etc/GMT0' => 'UTC',
'Etc/Greenwich' => 'UTC',
'Etc/UCT' => 'UTC',
'Etc/Universal' => 'UTC',
'Etc/UTC' => 'UTC',
'Etc/Zulu' => 'UTC',
'Factory' => 'UTC',
'GMT' => 'UTC',
'GMT+0' => 'UTC',
'GMT-0' => 'UTC',
'GMT0' => 'UTC',
'Hongkong' => 'Asia/Hong_Kong',
'HST' => 'Pacific/Honolulu',
'Iceland' => 'Atlantic/Reykjavik',
'Iran' => 'Asia/Tehran',
'Israel' => 'Asia/Tel_Aviv',
'Jamaica' => 'America/Jamaica',
'Japan' => 'Asia/Tokyo',
'Kwajalein' => 'Pacific/Kwajalein',
'Libya' => 'Africa/Tunis',
'MET' => 'Europe/Budapest',
'Mexico/BajaNorte' => 'America/Tijuana',
'Mexico/BajaSur' => 'America/Mazatlan',
'Mexico/General' => 'America/Mexico_City',
'MST' => 'America/Boise',
'MST7MDT' => 'America/Boise',
'Navajo' => 'America/Phoenix',
'NZ' => 'Pacific/Auckland',
'NZ-CHAT' => 'Pacific/Chatham',
'Poland' => 'Europe/Warsaw',
'Portugal' => 'Europe/Lisbon',
'PRC' => 'Asia/Chongqing',
'PST8PDT' => 'America/Los_Angeles',
'ROC' => 'Asia/Taipei',
'ROK' => 'Asia/Seoul',
'Singapore' => 'Asia/Singapore',
'Turkey' => 'Europe/Istanbul',
'US/Alaska' => 'America/Anchorage',
'US/Aleutian' => 'America/Adak',
'US/Arizona' => 'America/Phoenix',
'US/Central' => 'America/Chicago',
'US/East-Indiana' => 'America/Indianapolis',
'US/Eastern' => 'America/New_York',
'US/Hawaii' => 'Pacific/Honolulu',
'US/Indiana-Starke' => 'America/Indiana/Knox',
'US/Michigan' => 'America/Detroit',
'US/Mountain' => 'America/Boise',
'US/Pacific' => 'America/Los_Angeles',
'US/Pacific-New' => 'America/Los_Angeles',
'US/Samoa' => 'Pacific/Samoa',
'W-SU' => 'Europe/Moscow',
'WET' => 'Europe/Paris',
);
if (array_key_exists($old, $replace)) {
return $replace[$old];
}
else {
return $old;
}
}
/**
* These strings exist only for the extractor to pick them up and make them
* available for translation. Putting them here keeps them from being parsed
* on normal pages.
*
* Idea borrowed from Event module. Thanks killes!
*/
function date_timezone_translate() {
t('Africa/Algiers');
t('Africa/Asmera');
t('Africa/Bangui');
t('Africa/Blantyre');
t('Africa/Brazzaville');
t('Africa/Bujumbura');
t('Africa/Cairo');
t('Africa/Ceuta');
t('Africa/Dar_es_Salaam');
t('Africa/Djibouti');
t('Africa/Douala');
t('Africa/Gaborone');
t('Africa/Harare');
t('Africa/Johannesburg');
t('Africa/Kampala');
t('Africa/Khartoum');
t('Africa/Kigali');
t('Africa/Kinshasa');
t('Africa/Lagos');
t('Africa/Libreville');
t('Africa/Luanda');
t('Africa/Lubumbashi');
t('Africa/Lusaka');
t('Africa/Malabo');
t('Africa/Maputo');
t('Africa/Maseru');
t('Africa/Mbabane');
t('Africa/Mogadishu');
t('Africa/Nairobi');
t('Africa/Ndjamena');
t('Africa/Niamey');
t('Africa/Porto-Novo');
t('Africa/Tripoli');
t('Africa/Tunis');
t('Africa/Windhoek');
t('America/Adak');
t('America/Anchorage');
t('America/Anguilla');
t('America/Antigua');
t('America/Araguaina');
t('America/Aruba');
t('America/Asuncion');
t('America/Atka');
t('America/Barbados');
t('America/Belem');
t('America/Belize');
t('America/Boa_Vista');
t('America/Bogota');
t('America/Boise');
t('America/Buenos_Aires');
t('America/Cambridge_Bay');
t('America/Cancun');
t('America/Caracas');
t('America/Catamarca');
t('America/Cayenne');
t('America/Cayman');
t('America/Chicago');
t('America/Chihuahua');
t('America/Cordoba');
t('America/Costa_Rica');
t('America/Cuiaba');
t('America/Curacao');
t('America/Dawson');
t('America/Dawson_Creek');
t('America/Denver');
t('America/Detroit');
t('America/Dominica');
t('America/Edmonton');
t('America/Eirunepe');
t('America/El_Salvador');
t('America/Ensenada');
t('America/Fort_Wayne');
t('America/Fortaleza');
t('America/Glace_Bay');
t('America/Godthab');
t('America/Goose_Bay');
t('America/Grand_Turk');
t('America/Grenada');
t('America/Guadeloupe');
t('America/Guatemala');
t('America/Guayaquil');
t('America/Guyana');
t('America/Halifax');
t('America/Havana');
t('America/Hermosillo');
t('America/Indiana/Indianapolis');
t('America/Indiana/Knox');
t('America/Indiana/Marengo');
t('America/Indiana/Vevay');
t('America/Indianapolis');
t('America/Inuvik');
t('America/Iqaluit');
t('America/Jamaica');
t('America/Jujuy');
t('America/Juneau');
t('America/Kentucky/Louisville');
t('America/Kentucky/Monticello');
t('America/Knox_IN');
t('America/La_Paz');
t('America/Lima');
t('America/Los_Angeles');
t('America/Louisville');
t('America/Maceio');
t('America/Managua');
t('America/Manaus');
t('America/Martinique');
t('America/Mazatlan');
t('America/Mendoza');
t('America/Menominee');
t('America/Merida');
t('America/Mexico_City');
t('America/Miquelon');
t('America/Monterrey');
t('America/Montevideo');
t('America/Montreal');
t('America/Montserrat');
t('America/Nassau');
t('America/New_York');
t('America/Nipigon');
t('America/Nome');
t('America/Noronha');
t('America/Panama');
t('America/Pangnirtung');
t('America/Paramaribo');
t('America/Phoenix');
t('America/Port-au-Prince');
t('America/Port_of_Spain');
t('America/Porto_Acre');
t('America/Porto_Velho');
t('America/Puerto_Rico');
t('America/Rainy_River');
t('America/Rankin_Inlet');
t('America/Recife');
t('America/Regina');
t('America/Rio_Branco');
t('America/Rosario');
t('America/Santiago');
t('America/Santo_Domingo');
t('America/Sao_Paulo');
t('America/Scoresbysund');
t('America/Shiprock');
t('America/St_Johns');
t('America/St_Kitts');
t('America/St_Lucia');
t('America/St_Thomas');
t('America/St_Vincent');
t('America/Swift_Current');
t('America/Tegucigalpa');
t('America/Thule');
t('America/Thunder_Bay');
t('America/Tijuana');
t('America/Tortola');
t('America/Vancouver');
t('America/Virgin');
t('America/Whitehorse');
t('America/Winnipeg');
t('America/Yakutat');
t('America/Yellowknife');
t('Antarctica/Casey');
t('Antarctica/Davis');
t('Antarctica/DumontDUrville');
t('Antarctica/Mawson');
t('Antarctica/McMurdo');
t('Antarctica/Palmer');
t('Antarctica/South_Pole');
t('Antarctica/Syowa');
t('Antarctica/Vostok');
t('Arctic/Longyearbyen');
t('Asia/Aden');
t('Asia/Almaty');
t('Asia/Amman');
t('Asia/Anadyr');
t('Asia/Aqtau');
t('Asia/Aqtobe');
t('Asia/Ashgabat');
t('Asia/Ashkhabad');
t('Asia/Baghdad');
t('Asia/Bahrain');
t('Asia/Baku');
t('Asia/Bangkok');
t('Asia/Beirut');
t('Asia/Bishkek');
t('Asia/Brunei');
t('Asia/Calcutta');
t('Asia/Chungking');
t('Asia/Colombo');
t('Asia/Dacca');
t('Asia/Damascus');
t('Asia/Dhaka');
t('Asia/Dili');
t('Asia/Dubai');
t('Asia/Dushanbe');
t('Asia/Gaza');
t('Asia/Harbin');
t('Asia/Hong_Kong');
t('Asia/Hovd');
t('Asia/Irkutsk');
t('Asia/Istanbul');
t('Asia/Jakarta');
t('Asia/Jayapura');
t('Asia/Jerusalem');
t('Asia/Kabul');
t('Asia/Kamchatka');
t('Asia/Karachi');
t('Asia/Kashgar');
t('Asia/Katmandu');
t('Asia/Krasnoyarsk');
t('Asia/Kuala_Lumpur');
t('Asia/Kuching');
t('Asia/Kuwait');
t('Asia/Macao');
t('Asia/Magadan');
t('Asia/Manila');
t('Asia/Muscat');
t('Asia/Nicosia');
t('Asia/Novosibirsk');
t('Asia/Omsk');
t('Asia/Phnom_Penh');
t('Asia/Pyongyang');
t('Asia/Qatar');
t('Asia/Rangoon');
t('Asia/Riyadh');
t('Asia/Riyadh87');
t('Asia/Riyadh88');
t('Asia/Riyadh89');
t('Asia/Saigon');
t('Asia/Samarkand');
t('Asia/Seoul');
t('Asia/Shanghai');
t('Asia/Singapore');
t('Asia/Taipei');
t('Asia/Tashkent');
t('Asia/Tbilisi');
t('Asia/Tehran');
t('Asia/Tel_Aviv');
t('Asia/Thimbu');
t('Asia/Thimphu');
t('Asia/Tokyo');
t('Asia/Ujung_Pandang');
t('Asia/Ulaanbaatar');
t('Asia/Ulan_Bator');
t('Asia/Urumqi');
t('Asia/Vientiane');
t('Asia/Vladivostok');
t('Asia/Yakutsk');
t('Asia/Yekaterinburg');
t('Asia/Yerevan');
t('Atlantic/Azores');
t('Atlantic/Bermuda');
t('Atlantic/Canary');
t('Atlantic/Cape_Verde');
t('Atlantic/Faeroe');
t('Atlantic/Jan_Mayen');
t('Atlantic/Madeira');
t('Atlantic/South_Georgia');
t('Atlantic/Stanley');
t('Australia/ACT');
t('Australia/Adelaide');
t('Australia/Brisbane');
t('Australia/Broken_Hill');
t('Australia/Canberra');
t('Australia/Darwin');
t('Australia/Hobart');
t('Australia/LHI');
t('Australia/Lindeman');
t('Australia/Lord_Howe');
t('Australia/Melbourne');
t('Australia/NSW');
t('Australia/North');
t('Australia/Perth');
t('Australia/Queensland');
t('Australia/South');
t('Australia/Sydney');
t('Australia/Tasmania');
t('Australia/Victoria');
t('Australia/West');
t('Australia/Yancowinna');
t('Europe/Amsterdam');
t('Europe/Andorra');
t('Europe/Athens');
t('Europe/Belfast');
t('Europe/Belgrade');
t('Europe/Berlin');
t('Europe/Bratislava');
t('Europe/Brussels');
t('Europe/Bucharest');
t('Europe/Budapest');
t('Europe/Chisinau');
t('Europe/Copenhagen');
t('Europe/Dublin');
t('Europe/Gibraltar');
t('Europe/Helsinki');
t('Europe/Istanbul');
t('Europe/Kaliningrad');
t('Europe/Kiev');
t('Europe/Lisbon');
t('Europe/Ljubljana');
t('Europe/London');
t('Europe/Luxembourg');
t('Europe/Madrid');
t('Europe/Malta');
t('Europe/Minsk');
t('Europe/Monaco');
t('Europe/Moscow');
t('Europe/Nicosia');
t('Europe/Oslo');
t('Europe/Paris');
t('Europe/Prague');
t('Europe/Riga');
t('Europe/Rome');
t('Europe/Samara');
t('Europe/San_Marino');
t('Europe/Sarajevo');
t('Europe/Simferopol');
t('Europe/Skopje');
t('Europe/Sofia');
t('Europe/Stockholm');
t('Europe/Tallinn');
t('Europe/Tirane');
t('Europe/Tiraspol');
t('Europe/Uzhgorod');
t('Europe/Vaduz');
t('Europe/Vatican');
t('Europe/Vienna');
t('Europe/Vilnius');
t('Europe/Warsaw');
t('Europe/Zagreb');
t('Europe/Zaporozhye');
t('Europe/Zurich');
t('Indian/Antananarivo');
t('Indian/Chagos');
t('Indian/Christmas');
t('Indian/Cocos');
t('Indian/Comoro');
t('Indian/Kerguelen');
t('Indian/Mahe');
t('Indian/Maldives');
t('Indian/Mauritius');
t('Indian/Mayotte');
t('Indian/Reunion');
t('Pacific/Apia');
t('Pacific/Auckland');
t('Pacific/Chatham');
t('Pacific/Easter');
t('Pacific/Efate');
t('Pacific/Enderbury');
t('Pacific/Fakaofo');
t('Pacific/Fiji');
t('Pacific/Funafuti');
t('Pacific/Galapagos');
t('Pacific/Gambier');
t('Pacific/Guadalcanal');
t('Pacific/Guam');
t('Pacific/Honolulu');
t('Pacific/Johnston');
t('Pacific/Kiritimati');
t('Pacific/Kosrae');
t('Pacific/Kwajalein');
t('Pacific/Majuro');
t('Pacific/Marquesas');
t('Pacific/Midway');
t('Pacific/Nauru');
t('Pacific/Niue');
t('Pacific/Norfolk');
t('Pacific/Noumea');
t('Pacific/Pago_Pago');
t('Pacific/Palau');
t('Pacific/Pitcairn');
t('Pacific/Ponape');
t('Pacific/Port_Moresby');
t('Pacific/Rarotonga');
t('Pacific/Saipan');
t('Pacific/Samoa');
t('Pacific/Tahiti');
t('Pacific/Tarawa');
t('Pacific/Tongatapu');
t('Pacific/Truk');
t('Pacific/Wake');
t('Pacific/Wallis');
t('Pacific/Yap');
t('Pacific/French_Polynesia-Marquesas_Islands');
t('UTC');
}

View file

@ -0,0 +1,47 @@
/**
* Set the client's system time zone as default values of form fields.
*/
Drupal.setDefaultTimezone = function() {
var dateString = Date();
// In some client environments, date strings include a time zone
// abbreviation which can be interpreted by PHP.
var matches = Date().match(/\(([A-Z]{3,5})\)/);
var abbreviation = matches ? matches[1] : 0;
// For all other client environments, the abbreviation is set to "0"
// and the current offset from UTC and daylight saving time status are
// used to guess the time zone.
var dateNow = new Date();
var offsetNow = dateNow.getTimezoneOffset() * -60;
// Use January 1 and July 1 as test dates for determining daylight
// saving time status by comparing their offsets.
var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
var offsetJan = dateJan.getTimezoneOffset() * -60;
var offsetJul = dateJul.getTimezoneOffset() * -60;
// If the offset from UTC is identical on January 1 and July 1,
// assume daylight saving time is not used in this time zone.
if (offsetJan == offsetJul) {
var isDaylightSavingTime = '';
}
// If the maximum annual offset is equivalent to the current offset,
// assume daylight saving time is in effect.
else if (Math.max(offsetJan, offsetJul) == offsetNow) {
var isDaylightSavingTime = 1;
}
// Otherwise, assume daylight saving time is not in effect.
else {
var isDaylightSavingTime = 0;
}
// Submit request to the user/timezone callback and set the form field
// to the response time zone.
var path = 'user/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
$.getJSON(Drupal.settings.basePath, { q: path, date: dateString }, function (data) {
if (data) {
$("#edit-date-default-timezone, #edit-user-register-timezone, #edit-timezone-name").val(data);
}
});
};

View file

@ -0,0 +1,289 @@
<?php
/**
* @file
* This module will make the alter the user and site timezone forms to
* select a timezone name instead of a timezone offset.
*
* This module won't be needed once core starts tracking timezone names
* instead of offsets.
*/
/**
* Implementation of hook_menu().
*/
function date_timezone_menu() {
$items = array();
$items['user/timezone'] = array(
'title' => 'User timezone',
'page callback' => 'user_timezone',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implementation of hook_form_alter().
*
* Override system handling of user and site timezone selection.
*/
function date_timezone_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'system_date_time_settings') {
date_timezone_site_form($form);
if (!isset($form['#after_build'])) {
$form['#after_build'] = array();
}
$form['#after_build'][] = 'date_timezone_site_form_after_build';
}
elseif ($form_id == 'user_profile_form' && variable_get('configurable_timezones', 1) && isset($form['timezone'])) {
date_timezone_user_form($form);
if (!isset($form['#after_build'])) {
$form['#after_build'] = array();
}
$form['#after_build'][] = 'date_timezone_user_form_after_build';
}
}
/**
* Create a form for the site timezone names.
* Display a list of timezone names instead of offsets.
*/
function date_timezone_site_form(&$form) {
drupal_add_js(drupal_get_path('module', 'date_timezone') .'/date_timezone.js');
$form['locale']['#element_validate'] = array('date_timezone_update_site');
$timezone = variable_get('date_default_timezone_name', NULL);
$form['locale']['date_default_timezone_name'] = array(
'#type' => 'select',
'#title' => t('Default time zone'),
'#default_value' => $timezone,
'#options' => date_timezone_names(FALSE, TRUE), // Force an update before setting a site default.
'#description' => t('Select the default site time zone. If in doubt, choose the timezone that is closest to your location which has the same rules for daylight saving time.'),
'#weight' => -10,
'#offset' => variable_get('date_default_timezone', 0),
);
// Add the JavaScript callback to automatically set the timezone.
if (empty($timezone)) {
drupal_add_js('
// Global Killswitch
if (Drupal.jsEnabled) {
$(document).ready(function() {
Drupal.setDefaultTimezone();
});
}', 'inline');
}
return $form;
}
/**
* Hide the original form.
*
* We have to do this in after_build in case the Event module
* is enabled since the Event module will do its form_alter()
* after the Date module.
*/
function date_timezone_site_form_after_build(&$form) {
// Set the value, and make sure it's a legal value.
$value = $form['locale']['date_default_timezone']['#default_value'];
$options = $form['locale']['date_default_timezone']['#options'];
if (!array_key_exists($value, $options)) {
//$value = array_pop($options);
$value = NULL;
}
$form['locale']['date_default_timezone']['#type'] = 'hidden';
$form['locale']['date_default_timezone']['#value'] = $value;
return $form;
}
/**
* Create a form for the site timezone names.
* Display a list of timezone names instead of offsets.
*/
function date_timezone_user_form(&$form) {
drupal_add_js(drupal_get_path('module', 'date_timezone') .'/date_timezone.js');
$account = $form['_account']['#value'];
$form['timezone']['#uid'] = $account->uid;
$form['timezone']['#element_validate'] = array('date_timezone_update_user');
$timezone = $account->timezone_name ? $account->timezone_name : variable_get('date_default_timezone_name', NULL);
$form['timezone']['timezone_name'] = array(
'#type' => 'select',
'#title' => t('Default time zone'),
'#default_value' => $timezone,
'#options' => date_timezone_names(),
'#description' => t('Select your current local time. If in doubt, choose the timezone that is closest to your location which has the same rules for daylight saving time. Dates and times throughout this site will be displayed using this time zone.'),
);
// Add the JavaScript callback to automatically set the timezone.
if (empty($timezone)) {
drupal_add_js('
// Global Killswitch
if (Drupal.jsEnabled) {
$(document).ready(function() {
Drupal.setDefaultTimezone();
});
}', 'inline');
}
return $form;
}
/**
* Hide the original form.
*
* We have to do this in after_build in case the Event module
* is enabled since the Event module will do its form_alter()
* after the Date module.
*/
function date_timezone_user_form_after_build($form) {
// Set the value, and make sure it's a legal value.
$value = $form['timezone']['timezone']['#default_value'];
$options = $form['timezone']['timezone']['#options'];
if (!array_key_exists($value, $options)) {
//$value = array_pop($options);
$value = NULL;
}
$form['timezone']['timezone']['#type'] = 'hidden';
$form['timezone']['timezone']['#value'] = $value;
return $form;
}
/**
* Callback from site timezone settings form to update site timezone info.
* When the timezone name is updated, update the offset as well.
*/
function date_timezone_update_site($element, &$form_state) {
$timezone = $element['date_default_timezone_name']['#value'];
if (empty($timezone)) {
$offset = $element['date_default_timezone_name']['#offset'];
}
else {
variable_set('date_default_timezone_name', $timezone);
$date = date_make_date('now', $timezone);
$offset = date_offset_get($date);
}
// Reset the original form to the expected value.
if (module_exists('event') && db_table_exists('event_timezones')) {
$event_zone = date_event_zonelist_by_name(str_replace('_', ' ', $timezone));
// The event module will update the timezone and zone id, using this value.
if (!empty($event_zone['timezone'])) {
form_set_value($element['date_default_timezone'], $event_zone['timezone'] .'|'. $offset, $form_state);
} else {
form_set_value($element['date_default_timezone'], $offset, $form_state);
}
}
else {
form_set_value($element['date_default_timezone'], $offset, $form_state);
}
}
/**
* Callback from user timezone settings form to update user timezone info.
* When the timezone name is updated, update the offset as well.
*/
function date_timezone_update_user($element, &$form_state) {
$timezone = $element['timezone_name']['#value'];
if (!empty($timezone)) {
$date = date_make_date('now', $timezone);
$offset = date_offset_get($date);
}
// Reset the original form to the expected value.
if (module_exists('event') && db_table_exists('event_timezones')) {
$event_zone = date_event_zonelist_by_name(str_replace('_', ' ', $timezone));
// The event module will update the timezone and zone id using this value.
if (!empty($event_zone['timezone'])) {
form_set_value($element['timezone'], $event_zone['timezone'] .'|'. $offset, $form_state);
} else {
form_set_value($element['timezone'], $offset, $form_state);
}
}
else {
form_set_value($element['timezone'], $offset, $form_state);
}
}
/**
* Update the site timezone offset when cron runs.
*
* This is to make sure that modules that rely on the timezone offset
* have current information to process.
*/
function date_timezone_cron() {
$date = date_now(variable_get('date_default_timezone_name', NULL));
$offset = date_offset_get($date);
if ($offset != variable_get('date_default_timezone', 0)) {
variable_set('date_default_timezone', $offset);
}
}
/**
* Update user timezone information at login.
*
* This is to make sure that modules that rely on the timezone offset
* have current information to process.
*/
function date_timezone_user($op, &$edit, &$account, $category = NULL) {
if (isset($account->uid) && $op == 'login' && variable_get('configurable_timezones', 1)) {
if (strlen($account->timezone_name)) {
$date = date_now($account->timezone_name);
$offset = date_offset_get($date);
if ($offset != $account->timezone) {
$account->timezone = $offset;
db_query("UPDATE {users} SET timezone='%s' WHERE uid = %d", $offset, $account->uid);
}
}
else {
// If the user doesn't already have a timezone name selected,
// default it to the site timezone name and offset.
$timezone = variable_get('date_default_timezone_name', NULL);
if (!empty($timezone)) {
$date = date_now($timezone);
$offset = date_offset_get($date);
db_query("UPDATE {users} SET timezone_name = '%s', timezone='%s' WHERE uid = %d", $timezone, $offset, $account->uid);
}
}
}
}
/**
* Menu callback; Retrieve a JSON object containing a suggested time
* zone name.
*/
function user_timezone($abbreviation = '', $offset = -1, $is_daylight_saving_time = NULL) {
// An abbreviation of "0" passed in the callback arguments should be
// interpreted as the empty string.
$abbreviation = $abbreviation ? $abbreviation : '';
$timezone = function_exists('timezone_name_from_abbr') ? timezone_name_from_abbr($abbreviation, intval($offset), $is_daylight_saving_time) : 'UTC';
// The client date is passed in for debugging purposes.
$date = isset($_GET['date']) ? $_GET['date'] : '';
// Log a debug message.
watchdog('timezone', 'Detected time zone: %timezone; client date: %date; abbreviation: %abbreviation; offset: %offset; daylight saving time: %is_daylight_saving_time.', array('%timezone' => $timezone, '%date' => $date, '%abbreviation' => $abbreviation, '%offset' => $offset, '%is_daylight_saving_time' => $is_daylight_saving_time));
drupal_json($timezone);
}
/**
* Create replacement values for deprecated timezone names.
*/
function date_timezone_replacement($old) {
('./'. drupal_get_path('module', 'date_timezone') .'/date_timezone.install');
return _date_timezone_replacement($old);
}
/**
* Helper function to update Event module timezone information.
*/
function date_event_zonelist_by_name($name) {
if (!module_exists('event') || !db_table_exists('event_timezones')) {
return array();
}
static $zone_names = array();
if (!isset($zone_names[$name])) {
$zone = db_fetch_array(db_query("SELECT * FROM {event_timezones} WHERE name = '%s'", $name));
$zone_names[$name] = $zone;
}
return $zone_names[$name];
}

View file

@ -0,0 +1,142 @@
$content[type] = array(
'name' => 'Event',
'type' => 'event',
'description' => 'Events have a start date and an optional end date.',
'title_label' => 'Title',
'body_label' => 'Body',
'min_word_count' => '0',
'help' => '',
'node_options' =>
array(
'status' => true,
'promote' => true,
'sticky' => false,
'revision' => false,
),
'comment' => 2,
'old_type' => 'event',
'orig_type' => 'event',
'module' => 'node',
'custom' => '1',
'modified' => '1',
'locked' => '0',
);
$content[fields] = array(
0 =>
array(
'label' => 'Date',
'field_name' => 'field_event_date',
'type' => 'date',
'widget_type' => 'date_select',
'change' => 'Change basic information',
'weight' => '-2',
'default_value' => 'now',
'default_value_code' => '',
'default_value2' => 'blank',
'default_value_code2' => '',
'input_format' => 'Y-m-d H:i:s',
'input_format_custom' => '',
'year_range' => '-3:+3',
'increment' => '15',
'advanced' =>
array(
'label_position' => 'above',
'text_parts' =>
array(
'year' => 0,
'month' => 0,
'day' => 0,
'hour' => 0,
'minute' => 0,
'second' => 0,
),
),
'label_position' => 'above',
'text_parts' =>
array(
),
'description' => '',
'group' => false,
'required' => 0,
'multiple' => '0',
'repeat' => 0,
'todate' => 'optional',
'granularity' =>
array(
'year' => 'year',
'month' => 'month',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
),
'output_format_date' => 'l, j F, Y - H:i',
'output_format_custom' => '',
'output_format_date_long' => 'l, F j, Y - H:i',
'output_format_custom_long' => '',
'output_format_date_medium' => 'D, m/d/Y - H:i',
'output_format_custom_medium' => '',
'output_format_date_short' => 'm/d/Y - H:i',
'output_format_custom_short' => '',
'tz_handling' => 'date',
'timezone_db' => 'UTC',
'repeat_collapsed' => '0',
'op' => 'Save field settings',
'module' => 'date',
'widget_module' => 'date',
'columns' =>
array(
'value' =>
array(
'type' => 'varchar',
'length' => 20,
'not null' => false,
'sortable' => true,
),
'value2' =>
array(
'type' => 'varchar',
'length' => 20,
'not null' => false,
'sortable' => true,
),
'timezone' =>
array(
'type' => 'varchar',
'length' => 50,
'not null' => false,
'sortable' => true,
),
'offset' =>
array(
'type' => 'int',
'not null' => false,
'sortable' => true,
),
'offset2' =>
array(
'type' => 'int',
'not null' => false,
'sortable' => true,
),
),
'display_settings' =>
array(
'label' =>
array(
'format' => 'above',
),
'teaser' =>
array(
'format' => 'default',
),
'full' =>
array(
'format' => 'default',
),
4 =>
array(
'format' => 'default',
),
),
),
);

View file

@ -0,0 +1,171 @@
<?php
/**
* @file
* Code to change the type of a date.
*/
/**
* A form to change the type of date used in date fields.
*/
function date_tools_change_type_form() {
$form = array();
$fields = content_fields();
$date_options = array();
$type_options = array();
$labels = array();
foreach (date_field_info() as $type => $info) {
$type_options[$type] = $info['label'] .': '. $info['description'];
$labels[$type] = $info['label'];
}
// Get the available date fields.
foreach ($fields as $field_name => $field) {
if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') {
$date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array('@label' => $field['widget']['label'], '@field_name' => $field_name, '@type' => $labels[$field['type']]));
}
}
if (sizeof($date_options) < 1) {
drupal_set_message(t('There are no date fields in this database.'));
return $form;
}
$form['date_field'] = array(
'#type' => 'select',
'#options' => $date_options,
'#title' => t('Date field'),
'#default_value' => '',
'#description' => t('The date field which whose type should be changed.'),
);
$form['type'] = array(
'#type' => 'radios',
'#options' => $type_options,
'#default_value' => '',
'#required' => TRUE,
'#description' => t('The type of date to change the field to.'),
'#prefix' => '<strong>' . t('New type:') .'</strong>',
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Change'));
return $form;
}
function date_tools_change_type_form_validate($form, &$form_state) {
$field_name = $form_state['values']['date_field'];
$new_type = $form_state['values']['type'];
$field = content_fields($field_name);
$old_type = $field['type'];
if ($new_type == $old_type) {
form_set_error('type', t('The current type is the same as the chosen type. There is nothing to change.'));
}
}
function date_tools_change_type_form_submit($form, &$form_state) {
$field_name = $form_state['values']['date_field'];
$new_type = $form_state['values']['type'];
$field = content_fields($field_name);
$old_type = $field['type'];
if ($new_type == $old_type) {
return;
}
$db_info = content_database_info($field);
$table = $db_info['table'];
$columns = $db_info['columns'];
$labels = array();
foreach (date_field_info() as $type => $info) {
$labels[$type] = $info['label'];
}
// Is there any data in this field? If not, we can
// skip some steps.
$has_data = db_result(db_query("SELECT COUNT(*) FROM {". $table ."}"));
// Create a backup copy of the original values.
// The values are going to get corrupted when we
// change the column type.
if ($has_data) {
$temp_table = $table .'_temp';
db_query("CREATE TABLE {". $temp_table ."} SELECT * FROM {". $table ."}");
}
// Change the field definition to the new type.
$field['type'] = $new_type;
require_once('./'. drupal_get_path('module', 'content') .'/includes/content.crud.inc');
content_field_instance_update($field, FALSE);
content_clear_type_cache();
// If there's no data to update, we're finished.
if (!$has_data) {
drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array(
'@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type])));
return;
}
// Replace old values with modified values, massaging the
// original values as necessary for the new type.
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_sql.inc');
$date_handler = new date_sql_handler();
$date_handler->construct();
$date_handler->granularity = $field['granularity'];
$date_handler->date_type = $old_type;
$new_columns = array();
$old_columns = array('nid', 'vid');
$new_columns[] = $temp_table . '.nid AS nid';
$new_columns[] = $temp_table . '.vid AS vid';
if ($field->multiple) {
$new_columns[] = $temp_table . '.delta AS delta';
$old_columns[] = 'delta';
}
foreach ($columns as $column => $info) {
if ($column != 'value' && $column != 'value2') {
continue;
}
$old_columns[] = $info['column'];
$db_field = $date_handler->sql_field($temp_table . '.' . $info['column'], 0);
switch ($old_type) {
case 'date':
switch ($new_type) {
case 'datestamp':
$new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column'];
break;
case 'datetime':
$new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column'];
break;
}
break;
case 'datestamp':
switch ($new_type) {
case 'date':
$new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column'];
break;
case 'datetime':
$new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column'];
break;
}
break;
case 'datetime':
switch ($new_type) {
case 'date':
$new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column'];
break;
case 'datestamp':
$new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column'];
break;
}
break;
}
}
// Make sure database timezone is set to UTC.
$date_handler->set_db_timezone();
// Make the replacement.
$sql = 'REPLACE INTO {'. $table .'} ('. implode(', ', $old_columns) .') '.
' SELECT '. implode(', ', $new_columns) .' FROM {'. $temp_table .'}';
db_query($sql);
//dsm($sql);
db_query("DROP TABLE {". $temp_table ."}");
drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array(
'@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type])));
}

View file

@ -0,0 +1,388 @@
<?php
/**
* @file
* Code to migrate Events to use Date fields instead.
*/
/**
* Event import form.
*/
function date_tools_copy_import_event_form($form_state) {
// We can do an import if there are event fields available whether or not the event module is enabled
// so we just check whether the table exists.
if (!db_table_exists('event')) {
drupal_set_message(t('There is no event table in this database. No event import options are available.'));
return array();
}
if (empty($form_state['values']['step'])) {
$form_state['values']['step'] = 0;
}
$step = intval($form_state['values']['step'] + 1);
$form['step'] = array(
'#type' => 'hidden',
'#value' => $step,
);
switch ($step) {
case 1: // Select a content type to import into.
$node_types = node_get_types('names');
$form['#prefix'] = '<p>' . t("Create a new CCK content type to import your events into, or, if you do not want to create new nodes for your events, add a date field to the existing event type. Make sure the target content type has a date field that has an optional or required To date so it can accept the From date and To date of the event. If your source event has its own timezone field, make sure you set the target date timezone handling to 'date'. Test the target type by trying to create a node manually and make sure all the right options are available in the form before attempting an import.") . '</p><p><strong>' . t('The import will create new nodes and trigger all related hooks, so you may want to turn off automatic email messaging for this node type while performing the import!') . '</strong></p>';
$source_type_options = array();
$result = db_query("SELECT DISTINCT n.type FROM {event} e INNER JOIN {node} n ON e.nid=n.nid");
while ($arr = db_fetch_array($result)) {
$source_type_options[$arr['type']] = $node_types[$arr['type']];
}
if (sizeof($source_type_options) < 1) {
drupal_set_message(t('There are no event nodes in this database. No event import options are available.'));
return array();
}
$form['source_type'] = array(
'#type' => 'select',
'#options' => $source_type_options,
'#title' => t('Source type'),
'#default_value' => '',
);
$form += date_tools_copy_type_form(TRUE);
$form['submit'] = array('#type' => 'submit', '#value' => t('Import'));
return $form;
case 2: // Select the fields to import into.
$type = $form_state['values']['target_type'];
$form['target_type'] = array(
'#value' => $type,
'#type' => 'hidden',
);
$form['source_type'] = array(
'#value' => $form_state['values']['source_type'],
'#type' => 'hidden',
);
$form['fields'] = array(
'#type' => 'fieldset',
'#title' => t('!type Fields', array('!type' => check_plain($node_types[$type]))),
'#weight' => -1,
);
$form['fields'] += date_tools_copy_type_fields_form($type);
$form['delete_old'] = array('#type' => 'select', '#options' => array(1 => t('Yes'), 0 => t('No')), '#title' => t('Delete original event?'), '#description' => t('Should the original entry be deleted once it has been copied to the new content type? If so, be sure to back up your database first.'));
$form['max'] = array('#type' => 'textfield', '#title' => t('Limit'), '#description' => t('The maximum number of nodes to convert in this pass.'), '#required' => TRUE);
$form['start_nid'] = array('#type' => 'textfield', '#title' => t('Starting nid'), '#default_value' => 0, '#description' => t('Convert nodes with nids greater than or equal to this number.'));
$form['submit'] = array('#type' => 'submit', '#value' => t('Import'));
return $form;
}
}
/**
* Event import processing.
*/
function date_tools_copy_import_event_form_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
extract($form_state['values']);
if ($step != 2) return;
// workaround to disable drupal messages when nodes are created or deleted
//$messages = drupal_get_messages();
// The array that maps event timezone zids to timezone names is in
// date_php4_tz_map.inc, need to reverse it so the zid is the key.
require_once('./'. drupal_get_path('module', 'date_php4') .'/date_php4_tz_map.inc');
$timezones = array('' => '');
$map = $timezone_map;
foreach ($map as $zone => $values) {
if (!empty($values['zid'])) {
$timezones[$values['zid']] = $zone;
}
}
$rows = array();
$i = 0;
// Get $max records, 10 at a time.
$limit = min(10, intval($max));
while ($i < intval($max)) {
$new_rows = date_tools_copy_convert_events($source_type, $target_type, $date_field, $description_field, $limit, $i, $delete_old, $start_nid, $timezones);
$rows = array_merge($rows, $new_rows);
$i += $limit;
}
// write back the old messages
//$_SESSION['messages'] = $messages;
if (!empty($rows)) {
drupal_set_message(format_plural(sizeof($rows), '1 event has been converted.', '@count events have been converted.'));
drupal_set_message(theme('table', array(t('Node Title'), t('Original Node ID'), t('New Node ID'), t('Start date'), t('End date')), $rows));
}
else {
drupal_set_message(t('No events have been converted.'));
}
return;
}
function date_tools_copy_convert_events( $source_type, $target_type, $date_field, $description_field, $limit, $start = 0, $delete_old, $start_nid, $timezones) {
// Get info about the field we are importing into
$field = content_fields($date_field);
// Get date tz handling, could be date, site, GMT, or none.
$tz_handling = $field['tz_handling'];
// Get event tz handling, could be event, site, or user.
$event_tz_handling = variable_get('event_timezone_display', 'event');
// Check which version of the Event module this database was built in.
$event_version = 1;
if (db_column_exists('event', 'has_time')) {
$event_version = 2;
}
$rows = array();
if ($start_nid) {
// We're skipping the placeholders so make sure this is an integer.
$start_nid = intval($start_nid);
$where = " AND n.nid >= $start_nid ";
}
if (!$result = db_query_range("SELECT * FROM {event} e INNER JOIN {node} n ON e.nid=n.nid WHERE n.type = '%s' $where ORDER BY n.nid", array($source_type, $start_nid), $start, $limit)) {
return array();
}
while ($event = db_fetch_object($result)) {
$source_nid = $event->nid;
$event_node = node_load($source_nid, NULL, TRUE);
// Creating new nodes or converting existing ones??
if ($target_type != $source_type) {
$target_node = new stdClass();
$target_node->nid = 0;
$target_node->type = $target_type;
foreach ($event_node as $key => $val) {
if ($key != 'nid' && $key != 'type') {
$target_node->$key = $val;
}
}
}
else {
$target_node = $event_node;
}
if ($description_field != 'body') {
$target_node->$description_field = array(0 => array('value' => $event_node->body));
unset($target_node->body);
}
// Set the date timezone value.
$timezone = !empty($event->timezone) && $tz_handling == 'date' && $event_tz_handling == 'event' ? $timezones[$event->timezone] : date_default_timezone_name();
// If this is a deprecated timezone, replace it.
require_once(drupal_get_path('module', 'date_timezone') .'/date_timezone.install');
$timezone = _date_timezone_replacement($timezone);
// Find the original timezone value (might not be the same as the date timezone).
$event_timezone = !empty($event->timezone) ? $timezones[$event->timezone] : date_default_timezone_name();
// If this is a deprecated timezone, replace it.
$event_timezone = _date_timezone_replacement($event_timezone);
if ($event_version == 1) {
// Version 1 stores the UTC value in the database as a timestamp.
$date = array(0 => array());
$data[0]['timezone'] = $timezone;
$start = date_make_date($event->event_start, 'UTC', DATE_UNIX);
date_timezone_set($start, timezone_open($timezone));
$data[0]['offset'] = date_offset_get($start);
$end = date_make_date($event->event_end, 'UTC', DATE_UNIX);
date_timezone_set($end, timezone_open($timezone));
$data[0]['offset2'] = date_offset_get($end);
// If the original event had the wrong offset, the 'UTC' value it
// created will also be wrong, correct it here.
if ($event_node->start_offset != date_offset_get($start) || $event_node->end_offset != date_offset_get($end)) {
$adj = $event_node->start_offset - date_offset_get($start);
date_timezone_set($start, timezone_open('UTC'));
date_modify($start, $adj .' seconds');
$adj = $event_node->end_offset - date_offset_get($end);
date_timezone_set($end, timezone_open('UTC'));
date_modify($end, $adj .' seconds');
}
$data[0]['value'] = date_format($start, date_type_format($field['type']));
$data[0]['value2'] = date_format($end, date_type_format($field['type']));
}
else {
// Version 2 stores the local value in the database as a datetime field.
$date = array(0 => array());
$data[0]['timezone'] = $timezone;
$start = date_make_date($event->event_start, $event_timezone, DATE_DATETIME);
if ($event_timezone != $timezone) {
date_timezone_set($start, timezone_open($timezone));
}
$data[0]['offset'] = date_offset_get($start);
$end = date_make_date($event->event_end, $event_timezone, DATE_DATETIME);
if ($event_timezone != $timezone) {
date_timezone_set($end, timezone_open($timezone));
}
$data[0]['offset2'] = date_offset_get($end);
date_timezone_set($start, timezone_open('UTC'));
date_timezone_set($end, timezone_open('UTC'));
$data[0]['value'] = date_format($start, date_type_format($field['type']));
$data[0]['value2'] = date_format($end, date_type_format($field['type']));
}
$target_node->$date_field = $data;
$event_fields = array(
'event_start', 'event_end', 'timezone', 'start_offset',
'start_format', 'start_time_format', 'end_offset',
'end_format', 'end_time_format', 'event_node_title');
foreach ($event_fields as $e) {
unset($target_node->$e);
}
node_save($target_node);
if ($target_type != $source_type) {
watchdog('date_tools', '!type: %title has been created.', array(
'!type' => t($target_type),
'%title' => $target_node->title),
WATCHDOG_NOTICE,
l(t('view'), 'node/'. $target_node->nid));
if ($delete_old) {
node_delete($source_nid);
}
}
else {
watchdog('date_tools', '!type: %title has been updated.', array(
'!type' => t($target_type),
'%title' => $target_node->title),
WATCHDOG_NOTICE,
l(t('view'), 'node/'. $target_node->nid));
}
$new_field = $target_node->$date_field;
$rows[] = array(
l($target_node->title,
'node/'. $target_node->nid),
$source_nid,
$target_node->nid,
$new_field[0]['value'],
$new_field[0]['value2']);
}
return $rows;
}
/**
* A form to select a content type.
*/
function date_tools_copy_type_form($target = TRUE) {
$form = array();
$node_types = node_get_types('names');
$fields = content_fields();
$type_options = array();
// Find out what content types contain date fields and set them up as target options.
foreach ($fields as $field_name => $field) {
if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') {
$type_options[$field['type_name']] = $node_types[$field['type_name']];
}
}
if (sizeof($type_options) < 1) {
drupal_set_message(t('There are no date fields in this database to import the data into. Please add a date field to the desired node types and be sure to indicate it uses both a "from" and a "to" date.'));
return $form;
}
$type = $target ? 'target_type' : 'source_type';
$label = $target ? t('Target type') : t('Source type');
$form[$type] = array(
'#type' => 'select',
'#options' => $type_options,
'#title' => $label,
'#description' => t('Only content types with date fields appear in this list as possible target types.'),
'#default_value' => '',
);
// If Content Copy is enabled, offer an import link.
if (module_exists('content_copy')) {
$form['macro'] = array(
'#type' => 'fieldset',
'#title' => t('Add'),
'#description' => t('If your desired target type does not already have a date field, follow this link and select a content type to add a date field to that type.'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['macro']['link'] = array(
'#type' => 'markup',
'#value' => l(t('Add new date field'), 'admin/content/types/import', array('query' => 'macro_file='. drupal_get_path('module', 'date_tools') .'/date_field.php')),
);
}
return $form;
}
/**
* A form to select fields from a content type.
*/
function date_tools_copy_type_fields_form($type, $extended = FALSE) {
$form = array();
$fields = content_fields();
$date_options = array();
$description_options = array('' => '');
$uid_options = array('' => '');
$url_options = array('' => '');
$location_options = array('' => '');
// Find out what content types contain date fields and set them up as target options.
foreach ($fields as $field_name => $field) {
if ($field['type_name'] == $type) {
if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') {
$date_options[$field_name] = $field['widget']['label'];
}
if ($field['type'] == 'text') {
$description_options[$field_name] = $field['widget']['label'];
$location_options[$field_name] = $field['widget']['label'];
$uid_options[$field_name] = $field['widget']['label'];
$url_options[$field_name] = $field['widget']['label'];
}
if ($field['type'] == 'link') {
$url_options[$field_name] = $field['widget']['label'];
}
}
}
// The body field is also available as an option for the description.
$description_options['body'] = t('body');
if (sizeof($date_options) < 1) {
drupal_set_message(t('There are no date fields in this database to import the data into. Please add a date field to the desired node types and be sure to indicate it uses both a "from" and a "to" date.'));
return $form;
}
$form['date_field'] = array(
'#type' => 'select',
'#options' => $date_options,
'#title' => t('Date field'),
'#default_value' => '',
'#description' => t('The field which will contain the source dates in target content type.'),
);
$form['description_field'] = array(
'#type' => 'select',
'#options' => $description_options,
'#title' => t('Description field'),
'#default_value' => '',
'#description' => t('The text or body field which will contain the source description in the target content type.'),
);
if ($extended) {
$form['url_field'] = array(
'#type' => 'select',
'#options' => $url_options,
'#title' => t('Url field'),
'#default_value' => '',
'#description' => t('The text or link field which will contain the source url in the target content type.'),
);
$form['location_field'] = array(
'#type' => 'select',
'#options' => $location_options,
'#title' => t('Location field'),
'#default_value' => '',
'#description' => t('The text field which will contain the source location text in the target content type.'),
);
$form['uid_field'] = array(
'#type' => 'select',
'#options' => $uid_options,
'#title' => t('Uid field'),
'#default_value' => '',
'#description' => t('The text field which will contain the source uid in the target content type.'),
);
}
return $form;
}

View file

@ -0,0 +1,12 @@
name = Date Tools
description = Tools to import and auto-create dates and calendars.
dependencies[] = content
dependencies[] = date
package = Date/Time
core = 6.x
; Information added by Drupal.org packaging script on 2014-03-31
version = "6.x-2.10"
core = "6.x"
project = "date"
datestamp = "1396284252"

View file

@ -0,0 +1,173 @@
<?php
/**
* Implementation of hook_help().
*/
function date_tools_help($section, $arg) {
switch ($section) {
case 'admin/content/date/tools':
return '<p>'. t('Tools for creating and importing dates and calendars.') .'</p>';
case 'admin/content/date/tools/change':
return '<p>'. t('Change a date field from one type to another. Very experimental, use at your own risk!') .'</p>';
case 'admin/content/date/tools/date_wizard':
$disabled_modules = date_tools_wizard_disabled_modules(array('popup', 'repeat', 'linked'));
if (!empty($disabled_modules)) {
drupal_set_message(t('The following modules are required for the wizard to work:') .'<ul><li>'. implode('</li><li>', $disabled_modules) .'</li></ul>', 'error');
}
$output =
t('Fill out the following form to auto-create a date content type, with a datetime field and matching pre-configured calendar. A calendar and upcoming events block will be created, an ical feed will be added to the calendar, and the mini calendar, calendar legend, and upcoming events blocks will be added to the sidebar of the default theme. Nodes created from this new content type will include a link to the calendar, and the calendar will have a link to the \'add new date\' form. If the Signup module is enabled, Signups will also be enabled for this field. You can also add new date fields to an existing content type by entering the existing content type name instead of creating a new one.') .
'</p><p>'.
t('Only a limited set of options are displayed here to make this easy to set up. Once the date has been created you will be able to make other changes to the date settings and add other fields to your new content type on the Manage fields screen, and make changes to the calendar on the Views edit page.') .
'</p>';
return $output;
}
}
function date_tools_perm() {
return array('administer date tools');
}
function date_tools_menu() {
$items = array();
$items['admin/content/date/tools'] = array(
'title' => 'Date Tools',
'description' => 'Tools to import and auto-create dates and calendars.',
'access arguments' => array('administer date tools'),
'page callback' => 'date_tools_page',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/content/date/tools/about'] = array(
'title' => 'About',
'description' => 'Tools to import and auto-create dates and calendars.',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -5,
'priority' => 1,
'page callback' => 'date_tools_page',
'access arguments' => array('administer date tools'),
);
$items['admin/content/date/tools/date_wizard'] = array(
'title' => 'Date wizard',
'description' => 'Easy creation of date content types and calendars.',
'type' => MENU_LOCAL_TASK,
'weight' => 1,
'priority' => 1,
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_wizard_form'),
'access arguments' => array('administer date tools'),
'file' => 'date_tools.wizard.inc',
'path' => drupal_get_path('module', 'date_tools'),
);
$items['admin/content/date/tools/import'] = array(
'title' => 'Import',
'access arguments' => array('administer date tools'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_copy_import_event_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'file' => 'date_tools.event.inc',
'path' => drupal_get_path('module', 'date_tools'),
);
$items['admin/content/date/tools/import/event'] = array(
'title' => 'Event import',
'access arguments' => array('administer date tools'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_copy_import_event_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
'file' => 'date_tools.event.inc',
'path' => drupal_get_path('module', 'date_tools'),
);
$items['date/tools/remove'] = array(
'title' => 'Remove calendar',
'access arguments' => array('administer date tools'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_remove_form', 3),
'type' => MENU_CALLBACK,
);
$items['admin/content/date/tools/change'] = array(
'title' => 'Change',
'access arguments' => array('administer date tools'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_change_type_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
'file' => 'date_tools.change_type.inc',
'path' => drupal_get_path('module', 'date_tools'),
);
return $items;
}
/**
* Main Date Tools page
*/
function date_tools_page() {
$choices = array();
$choices[] = t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar.', array('!date_wizard' => l(t('Date wizard'), 'admin/content/date/tools/date_wizard')));
$content = '<ul><li>'. implode('</li><li>', $choices) .'</li></ul>';
if (module_exists('calendar')) {
$calendar_options = variable_get('calendar_default_view_options', array());
$calendars = array();
$node_types = node_get_types();
foreach ($calendar_options as $key => $option) {
$type_name = str_replace('calendar_', '', $key);
if (array_key_exists($type_name, $node_types)) {
$type = $node_types[$type_name];
$calendars[] = array(
l($type->name, 'admin/content/node-type/'. $type_name .'/fields'),
l($key, 'admin/build/views/edit/'. $key),
t('The calendar %view is a default calendar created for the content type %type_name.', array('%view' => $key, '%type_name' => $type->name)),
l(t('remove !view', array('!view' => $key)), 'date/tools/remove/' . $key),
);
}
else {
// Do some cleanup while we're here if we have default calendars
// for non-existent content types.
calendar_remove($type_name);
}
}
if (!empty($calendars)) {
$headers = array(t('Content Type'), t('Calendar'), t('Description'), t('Operations'));
$content .= theme('table', $headers, $calendars);
}
}
return $content;
}
/**
* Menu callback; present a form for removing a field from a content type.
*/
function date_tools_remove_form(&$form_state, $view_name) {
$form = array();
$form['view_name'] = array(
'#type' => 'value',
'#value' => $view_name,
);
$output = confirm_form($form,
t('Are you sure you want to remove the view %view?', array('%view' => $view_name)),
'admin/content/date/tools',
t('This action cannot be undone.'),
t('Remove'), t('Cancel')
);
return $output;
}
/**
* Remove a field from a content type.
*/
function date_tools_remove_form_submit($form, &$form_state) {
$form_values = $form_state['values'];
$view_name = $form_values['view_name'];
if ($view_name && $form_values['confirm']) {
calendar_remove($view_name);
drupal_set_message(t('Removed calendar %calendar.', array(
'%calendar' => $view_name)));
}
$form_state['redirect'] = 'admin/content/date/tools';
}

View file

@ -0,0 +1,527 @@
<?php
/**
* @file
* Date Wizard code.
*/
function date_tools_wizard_form() {
$form = array();
$form['type'] = array(
'#type' => 'fieldset',
'#title' => t('Content type'),
);
$form['type']['type_name'] = array(
'#type' => 'textfield',
'#default_value' => 'date',
'#title' => t('Content type name'),
'#description' => t('Machine-readable name. Allowed values: (a-z, 0-9, _). If this is not an existing content type, the content type will be created.'),
);
$form['type']['name'] = array(
'#type' => 'textfield',
'#default_value' => t('Date'),
'#title' => t('Content type label'),
'#description' => t('The human-readable name for this content type. Only needed when creating a new content type.'),
);
$form['type']['type_description'] = array(
'#type' => 'textarea',
'#default_value' => t('A date content type that is linked to a Views calendar.'),
'#title' => t('Content type description'),
'#description' => t('A description for the content type. Only needed when creating a new content type.'),
);
$form['field'] = array(
'#type' => 'fieldset',
'#title' => t('Date field'),
);
$form['field']['field_name'] = array(
'#type' => 'textfield',
'#default_value' => 'date',
'#field_prefix' => 'field_',
'#title' => t('Date field name'),
'#description' => t('Machine-readable name. Allowed values: (a-z, 0-9, _) Must not be an existing field name.'),
);
$form['field']['label'] = array(
'#tree' => TRUE,
'#type' => 'textfield',
'#default_value' => t('Date'),
'#title' => t('Date field label'),
'#description' => t('The human-readable label for this field.'),
);
$form['field']['widget_type'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_widget_types(),
'#default_value' => 'date_select',
'#title' => t('Date widget type'),
);
$form['field']['repeat'] = array(
'#type' => 'select',
'#default_value' => 0,
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#title' => t('Show repeating date options'),
'#access' => module_exists('date_repeat'),
);
$form['field']['advanced'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Advanced options'),
);
$form['field']['advanced']['field_type'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_field_types(),
'#default_value' => 'datetime',
'#title' => t('Date field type'),
'#description' => t("The recommend type is Datetime, except for historical dates or dates with only year or month granularity. Older or incomplete dates should use the Date type (an ISO date)."),
);
$form['field']['advanced']['granularity'] = array(
'#type' => 'select',
'#options' => date_granularity_names(),
'#default_value' => array('month', 'day', 'year', 'hour', 'minute'),
'#title' => t('Granularity'),
'#multiple' => TRUE,
);
$form['field']['advanced']['tz_handling'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_tz_handling(),
'#default_value' => 'site',
'#title' => t('Date timezone handling'),
'#description' => t("Timezone handling should be set to 'none' for granularity without time elements.")
);
$form['calendar'] = array(
'#type' => 'select',
'#default_value' => 1,
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#title' => t('Create a calendar for this date field'),
'#access' => module_exists('calendar'),
);
$form['blocks'] = array(
'#type' => 'select',
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#default_value' => 0,
'#title' => t('Add calendar blocks to the current theme'),
'#access' => module_exists('calendar'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
function date_tools_wizard_form_validate(&$form, &$form_state) {
$type_name = $form_state['values']['type_name'];
$field_name = 'field_'. $form_state['values']['field_name'];
if (db_result(db_query("SELECT type FROM {node_type} WHERE type='%s'", $type_name))) {
drupal_set_message(t('This content type name already exists, adding new field to existing content type.'));
}
if (!preg_match('!^[a-z0-9_]+$!', $type_name)) {
form_set_error('type_name', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
}
if (db_result(db_query("SELECT field_name FROM {content_node_field_instance} WHERE field_name='%s' AND type_name='%s'", $field_name, $type_name))) {
form_set_error('field_name', t('This field name already exists.'));
}
if (!date_has_time($form_state['values']['granularity']) && $form_state['values']['tz_handling'] != 'none') {
form_set_error('tz_handling', t('Timezone handling must be none for granularity without time.'));
}
}
function date_tools_wizard_form_submit(&$form, &$form_state) {
$view_name = date_tools_wizard_build($form_state['values']);
menu_rebuild();
if (!empty($view_name)) {
drupal_set_message(t('Change the calendar as needed and save the view.'), 'error');
$form['#redirect'] = 'admin/build/views/edit/'. $view_name;
}
else {
$form['#redirect'] = 'admin/content/node-type/'. str_replace('_', '-', $form_state['values']['type_name']) .'/fields';
}
}
function date_tools_wizard_build($form_values) {
extract($form_values);
$field_name = 'field_'. $field_name;
if (!empty($repeat)) {
$widget_type .= '_repeat';
}
// Create a node type if it doesn't already exist.
// This makes it possible to add additional date fields to
// an existing type.
$types = node_get_types('names');
$type_settings = array();
if (!array_key_exists($type_name, $types)) {
date_tools_wizard_create_content_type($name, $type_name, $type_description, $type_settings);
}
else {
$types = node_get_types();
$type = $types[$type_name];
if (!empty($type_settings)) {
foreach ($type_settings as $key => $setting) {
$type->$key = $setting;
}
node_type_save($type);
}
$name = $type->name;
}
$field_settings = array(
'field_name' => $field_name,
'label' => $label,
'type_name' => $type_name,
'type' => $field_type,
'widget_type' => $widget_type,
'granularity' => $granularity,
'tz_handling' => $tz_handling,
'repeat' => $repeat,
'multiple' => $repeat,
);
$field_settings['widget']['label'] = $label;
$field_settings['widget']['type'] = $widget_type;
$date_field = date_tools_wizard_create_date_field($field_type, $widget_type, $tz_handling, $field_settings);
$view_name = '';
if (!empty($calendar)) {
$view_name = date_tools_wizard_create_calendar($name, $type_name, $date_field);
if (!empty($blocks)) {
date_tools_wizard_create_blocks($type_name);
}
}
return $view_name;
}
function date_tools_wizard_include() {
module_load_include('inc', 'node', 'content_types');
module_load_include('inc', 'node', 'node.pages');
module_load_include('inc', 'content', 'includes/content.admin');
module_load_include('inc', 'content', 'includes/content.crud');
module_load_include('inc', 'date', 'date_admin');
}
function date_tools_wizard_options($options = array()) {
$default_options = array();
if (module_exists('date_popup')) {
$default_options[] = 'popups';
}
if (module_exists('date_repeat')) {
$default_options[] = 'repeat';
}
if (module_exists('calendar')) {
$default_options[] = 'linked';
}
// Allow override of default options.
if (!empty($options)) {
return $options;
}
else {
return $default_options;
}
}
/**
* Return an array of the modules needed by this wizard.
*/
function date_tools_wizard_required_modules($options = array()) {
$options = date_tools_wizard_options($options);
$modules = array(
'date_timezone', 'date_api', 'content', 'date',
);
if (module_exists('calendar')) {
$modules = array_merge($modules, array('calendar', 'views', 'views_ui'));
}
if (in_array('popups', $options)) {
$modules = array_merge($modules, array('date_popup', 'jcalendar'));
}
if (in_array('repeat', $options)) {
$modules = array_merge($modules, array('date_repeat'));
}
return $modules;
}
function date_tools_wizard_modules() {
return array(
'date' => t('Date'),
'date_api' => t('Date API'),
'date_timezone' => t('Date Timezone'),
'content' => t('Content'),
'calendar' => t('Calendar'),
'calendar_ical' => t('Calendar iCal'),
'date_repeat' => t('Date Repeat'),
'date_popup' => t('Date Popup'),
'jcalendar' => t('Calendar Popup'),
'text' => t('Text'),
'optionwidgets' => t('Optionwidgets'),
'nodereference' => t('Nodereference'),
'views' => t('Views'),
'views_ui' => t('Views UI'),
);
}
function date_tools_wizard_enabled_modules($options = array()) {
$modules = date_tools_wizard_required_modules($options);
$enabled = array();
$names = date_tools_wizard_modules();
foreach ($modules as $option) {
if (module_exists($option)) {
$enabled[$option] = $names[$option] .' (<span class="admin-enabled">'. t('enabled') .'</span>)';
}
}
return $enabled;
}
function date_tools_wizard_disabled_modules($options = array()) {
$modules = date_tools_wizard_required_modules($options);
$enabled = array();
$names = date_tools_wizard_modules();
foreach ($modules as $option) {
if (!module_exists($option)) {
$enabled[$option] = $names[$option] .' (<span class="admin-disabled">'. t('disabled') .'</span>)';
}
}
return $enabled;
}
function date_tools_wizard_field_types() {
$field_types = array();
foreach (date_field_info() as $name => $info) {
$field_types[$name] = $info['label']; //.' - '. $info['description'];
}
return $field_types;
}
function date_tools_wizard_widget_types() {
$widget_types = array();
foreach (date_widget_info() as $name => $info) {
if (!strstr($name, '_repeat')) {
$widget_types[$name] = $info['label'];
}
}
return $widget_types;
}
function date_tools_wizard_tz_handling() {
include_once(drupal_get_path('module', 'date') .'/date_admin.inc');
return date_timezone_handling_options();
}
function date_tools_wizard_create_content_type($name, $type_name, $description, $type_settings = array()) {
date_tools_wizard_include();
// Create the content type.
$values = array(
'name' => $name,
'type' => $type_name,
'description' => $description,
'title_label' => 'Title',
'body_label' => 'Body',
'min_word_count' => '0',
'help' => '',
'node_options' =>
array(
'status' => 1,
'promote' => 1,
'sticky' => 0,
'revision' => 0,
),
'language_content_type' => '0',
'old_type' => $type_name,
'orig_type' => '',
'module' => 'node',
'custom' => '1',
'modified' => '1',
'locked' => '0',
'url_str' => str_replace('_', '-', $type_name),
);
// Allow overrides of these values.
foreach ($type_settings as $key => $value) {
$values[$key] = $value;
}
$type = (object) _node_type_set_defaults($values);
node_type_save($type);
if ($type == 'ical_feed') {
// Default type to not be promoted and have comments disabled.
variable_set('node_options_'. $type_name, array('status'));
variable_set('comment_'. $type_name, COMMENT_NODE_DISABLED);
// Don't display date and author information for this type by default.
$theme_settings = variable_get('theme_settings', array());
$theme_settings['toggle_node_info_'. $type_name] = FALSE;
variable_set('theme_settings', $theme_settings);
}
// Update the menu router information.
menu_rebuild();
content_clear_type_cache();
}
function date_tools_wizard_create_date_field($field_type, $widget_type, $tz_handling, $overrides = array()) {
date_tools_wizard_include();
$field_name = $overrides['field_name'];
$type_name = $overrides['type_name'];
// Create the date field.
// Set overrides that apply to all date fields.
$base_overrides = array(
'field_name' => $field_name,
'type_name' => $type_name,
// Can be set to 0 (none), 1 (unlimited or repeating),
// or 2-9 for a fixed number of values.
'multiple' => 0,
// Set a display format that will show complete date information,
// to help test that values are correct.
'default_format' => 'long',
// Set the date granularity.
'granularity' => array(
'year' => 'year',
'month' => 'month',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
),
// Shall this date include a 'To date'?, can be blank, 'optional', or 'required'
'todate' => 'optional',
);
// Widget overrides:
$widget_overrides = array(
// Move the date right below the title.
'weight' => -4,
// Set default values to 'blank', 'now', or 'relative'. To dates can also use 'same'.
'default_value' => 'now',
'default_value_code' => '', // The code to use with 'relative', like '+1 day'.
'default_value2' => 'blank',
'default_value_code2' => '', // The code to use with 'relative', like '+1 day'.
// Set format string to use for input forms, it controls order and type of date parts.
// Input formats must have all date parts, including seconds.
'input_format_custom' => variable_get('date_format_short', 'm/d/Y - H:i') .':s',
// Increment for minutes and seconds, can be 1, 5, 10, 15, or 30.
'increment' => 15,
// Optional array of date parts that should be textfields in select widgets,
// can be any combination of year, month, day, hour, minute, second.
'text_parts' => array(),
// The number of years to go back and forward in drop-down year selectors.
'year_range' => '0:+1',
// The place for the date part labels, can be 'above', 'within', or 'none'.
'label_position' => 'above',
);
$overrides = array_merge($base_overrides, $overrides);
$overrides['widget'] = array_merge($widget_overrides, $overrides['widget']);
// Get field default values for this combination of field and widget type,
// using a helper function in the Date module.
$field = date_field_default_values($field_type, $widget_type, $tz_handling, $overrides);
$field = content_field_instance_collapse($field);
$field = content_field_instance_create($field);
$field = content_field_instance_expand($field);
return $field;
}
function date_tools_wizard_create_calendar($name, $type_name, $date_field) {
$date_name = !empty($date_field) ? $date_field['field_name'] : '';
$path = 'calendar-'. str_replace('field_', '', $date_name);
// Add a default calendar for this content type.
$calendar_options = variable_get('calendar_default_view_options', array());
if (array_key_exists('calendar_'. $type_name, $calendar_options)) {
unset($calendar_options['calendar_'. $type_name]);
}
$option = array(
'name' => 'calendar_'. $type_name,
'description' => 'An event calendar for '. $date_field['widget']['label'],
'path' => $path,
'types' => array($type_name => $name),
'date_fields' => array($date_name),
'display_fields' => array('title' => array(), $date_name => array()),
);
$calendar_options['calendar_'. $type_name] = $option;
variable_set('calendar_default_view_options', $calendar_options);
// Make sure menu items get rebuilt as necessary.
menu_rebuild();
// Clear the views cache.
cache_clear_all('*', 'cache_views');
// Clear the page cache.
cache_clear_all();
// Remove this view from cache so we can edit it properly.
views_object_cache_clear('view', 'calendar_'. $type_name);
// Force Views to empty its cache and find the new view.
views_discover_default_views();
return 'calendar_'. $type_name;
}
function date_tools_wizard_create_blocks($type_name) {
// Add calendar blocks to the default theme.
$current_theme = variable_get('theme_default', 'garland');
// Legend block.
$block = new stdClass();
$block->theme = $current_theme;
$block->status = 1;
$block->weight = -1;
$block->region = 'left';
$block->title = '';
$block->module = 'calendar';
$block->delta = 0;
date_tools_wizard_add_block($block);
// Mini calendar block.
$block->module = 'views';
$block->delta = 'calendar_'. $type_name .'-calendar_block_1';
date_tools_wizard_add_block($block);
// Upcoming events block.
$block->module = 'views';
$block->delta = 'calendar_'. $type_name .'-block_1';
date_tools_wizard_add_block($block);
return;
}
/**
* Add a block.
*/
function date_tools_wizard_add_block($block) {
$bid = db_result(db_query("SELECT bid FROM {blocks} WHERE module='%s' AND delta='%s' AND theme='%s'", $block->module, $block->delta, $block->theme));
if (empty($bid)) {
$block->bid = NULL;
drupal_write_record('blocks', $block);
}
else {
$block->bid = $bid;
drupal_write_record('blocks', $block, 'bid');
}
}

View file

@ -0,0 +1,137 @@
<h3>Preconfigured arrays</h3>
<p>
Both translated and untranslated values are available. The date_week_days_ordered()
function will shift an array of week day names so it starts with the site's
first day of the week, otherwise the weekday names start with Sunday as the first
value, the expected order for many php and sql functions.
</p>
<ul style="color: rgb(0, 0, 187);">
<li>
date_month_names();
</li><li>
date_month_names_abbr();
</li><li>
date_month_names_untranslated();
</li><li>
date_week_days();
</li><li>
date_week_days_abbr();
</li><li>
date_week_days_untranslated();
</li><li>
date_week_days_ordered();
</li><li>
date_years();
</li><li>
date_hours();
</li><li>
date_minutes();
</li><li>
date_seconds();
</li><li>
date_timezone_names();
</li><li>
date_ampm();
</li>
</ul>
<h3>Miscellaneous date manipulation functions </h3>
<p>
Pre-defined constants and functions that will handle pre-1970 and post-2038
dates in both PHP 4 and PHP 5, in any OS. Dates can be converted from one
type to another and date parts can be extracted from any date type.
</p>
<ul style="color: rgb(0, 0, 187);">
<li>
DATE_DATETIME
</li><li>
DATE_ISO
</li><li>
DATE_UNIX
</li><li>
DATE_ARRAY
</li><li>
DATE_OBJECT
</li><li>
DATE_ICAL
</li><li>
date_convert()
</li><li>
date_is_valid();
</li><li>
date_part_is_valid();
</li><li>
date_part_extract();
</li>
</ul>
<h3>Date calculation and navigation</h3>
<p>
date_diff() will find the time difference between any two days, measured
in seconds, minutes, hours, days, months, weeks, or years.
</p>
<ul style="color: rgb(0, 0, 187);">
<li>
date_days_in_month();
</li><li>
date_days_in_year();
</li><li>
date_weeks_in_year();
</li><li>
date_last_day_of_month();
</li><li>
date_day_of_week();
</li><li>
date_day_of_week_name();
</li><li>
date_diff();
</li>
</ul>
<h3>Date regex and format helpers </h3>
<p>
Pre-defined constants, an array of date format strings and their
equivalent regex strings.
</p>
<p>
DATE_REGEX_LOOSE is a very loose regex that will pull date parts out
of an ISO date with or without separators, using either 'T' or a space
to separate date and time, and with or without time.
</p>
<p>
date_format_date() is similar to format_date(), except it takes a
date object instead of a timestamp as the first parameter.
</p>
<ul style="color: rgb(0, 0, 187);">
<li>
DATE_FORMAT_ISO
</li><li>
DATE_FORMAT_DATETIME
</li><li>
DATE_FORMAT_UNIX
</li><li>
DATE_FORMAT_ICAL
</li><li>
DATE_REGEX_ISO
</li><li>
DATE_REGEX_DATETIME
</li><li>
DATE_REGEX_LOOSE
</li><li>
date_format_date();
</li><li>
date_t()
</li><li>
date_short_formats();
</li><li>
date_medium_formats();
</li><li>
date_long_formats();
</li><li>
date_format_patterns();
</li>
</ul>

View file

@ -0,0 +1,23 @@
<p>
Once the Date API is installed, all functions in the API are available to be used
anywhere by any module. If the Date Timezone module is installed, the system site
timezone selector and the user timezone selectors are overwritten to allow the
selection of timezone names instead of offsets. Proper timezone conversion
requires knowledge of those timezone names, something that is not currently
available in Drupal core, and the change in selectors makes it possible to track it.
</p>
<p>
In most cases, you should enable the Date Timezone module any time you use the
Date API to be able to set the site and user timezone names. It is not enabled by
default in case another module is setting timezone names in the database.
</p>
<p>
The API uses the PHP 5.2 date functions to create and manipulate dates, and
contains an option module that will emulate those functions in earlier versions
of PHP.
</p>
<p>
If you are using PHP 4 or PHP 5.0 or 5.1, native date handling won't work right.
Install the Date_PHP4 module to enable wrapper functions so this code will work
in old PHP versions.
</p>

View file

@ -0,0 +1,31 @@
<p>The generic date argument is based on ISO 8601 date duration and time interval standards, and allows you to create an flexible argument for any date range for any date field.
</p><p>The argument expects a value like 2006-01-01--2006-01-15, or 2006-W24, or @P1W. Separate from and to dates or date and period with a double hyphen (--)
</p><p>From and to dates in argument are ISO dates, but can be shortened and missing parts will be added. Omitted parts of ISO dates will be assumed to be the first possible (for the from date) or the last possible (for the to date) value in that time period.
</p><p>The 'to' portion of the argument can be eliminated if it is the same as the 'from' portion Use @ instead of a date to substitute in the current date and time.
</p><p>Shortcuts are available:
</p><p>Use periods (P1H, P1D, P1W, P1M, P1Y) to get next hour/day/week/month/year from now. Use date before P sign to get next hour/day/week/month/year from that date. The ISO standard calls for a separator (--) between a date and the P, but the separator is optional between a start date and a period in this argument to make the result easier to read.
</p><p>Use format like 2006-W24 to find ISO week number 24 in year 2006.</p>
<h3>Examples:</h3>
<table border="1">
<tr><th>ARGUMENT</th><th>RESULTING QUERY RANGE</th></tr>
<tr><td>2006-W24</td><td>24th ISO week in 2006</td>
</tr><tr><td>2006</td><td>the whole year of 2006</td>
</tr><tr><td>2006-03</td><td>the whole month of Mar 2006</td>
</tr><tr><td>2006-02--2007-03</td><td>Feb 1 2006 to Mar 31 2006</td>
</tr><tr><td>2006-08-31T14--2006-08-31T16</td><td>the 14th to 16th hours of Aug 8 2006</td>
</tr><tr><td>@--2006-12-31</td><td>NOW to 2006-12-31T23:59:59</td>
</tr><tr><td>@P3H</td><td>NOW to three hours from now</td>
</tr><tr><td>@P1Y90D</td><td>NOW to 1 year and 90 days from now</td>
</tr><tr><td>2006-03-05P1W</td><td>the week starting Mar 5 2006</td>
</tr><tr><td>2006-01P3M</td><td>3 months starting Jan 2006</td>
</tr></table>
<p>See <a href="http://en.wikipedia.org/wiki/ISO_8601#Week_dates">http://en.wikipedia.org/wiki/ISO_8601#Week_dates</a> for definitions of ISO weeks See <a href="http://en.wikipedia.org/wiki/ISO_8601#Duration">http://en.wikipedia.org/wiki/ISO_8601#Duration</a> for definitions of ISO duration and time interval.
</p>

View file

@ -0,0 +1,13 @@
The Date Copy module allows you to import date data into CCK nodes. Currently you can convert Event nodes to CCK nodes or import date data from an iCal feed.
<h3>Importing from Event Nodes</h3>
If you currently have date information in Event nodes and want to get the event dates into CCK Date fields instead, you can do this using the Date Copy module included with the Date module.
<ol>
<li>First, create the content type you want to import your events into and make sure the content type has a CCK date field and a field to hold the event description (either a body or a text field). You can create a new content type for this purpose or add a CCK date field to the current event type, depending on whether you want to create new nodes and nids or use the current ones.</li>
<li>Make sure you set up the CCK date field to have both a 'From Date' and a 'To Date'. If your events have event-specific timezones, make sure the CCK date field is set up to use the date's timezone.</li>
<li>Go to Admin >> Content management >> Date Import/Export >> Import >> Events and select 'Events' as the content type to import from and your new content type as the type to import to. Only content types that have date fields will appear in the list of available content types.</li>
<li>If you have added the new date fields to the current content type, be sure to select the option to NOT delete the old node after the import. If you have created a new content type, you will probably want to delete the old nodes.</li>
<li>You can select the number of items to import and the starting nid (import will be done in ascending nid order). Use that to import a single node as a test to be sure the results are what you want.</li>
<li>Once your nodes seem to be importing correctly, go ahead and import more of them. You may want to do them in batches to keep the server from timing out.</li>
</ol>

View file

@ -0,0 +1,21 @@
<p>The DateTime object is defined in PHP versions 5.2 and greater. Full documentation can be found at <a href="http://php.net/datetime">php.net/datetime</a>.</p>
<p>PHP's <strong>print_r()</strong>, <a href="http://drupal.org/project/devel">Devel module</a>'s <strong>dpm()</strong>, and similar functions and most debuggers are not able to show the properties of DateTime objects. The <strong>date_format()</strong> function provides a good alternate method to debug DateTime objects. An example of its usage follows, including some common DateTime manipulation functions;</p>
<p>
<!-- PHP colorized by input filter on Drupal.org, from http://drupal.org/node/337764#comment-1132868 -->
<span style="color: rgb(0, 0, 0);"><span style="color: rgb(0, 0, 187);">&lt;?php<br /></span><span style="color: rgb(255, 128, 0);">// Create a DateTime object for 11 November 2008, 5pm, Chicago time.<br /></span><span style="color: rgb(0, 0, 187);">$date</span> <span style="color: rgb(0, 119, 0);">=</span> <span style="color: rgb(0, 0, 187);">date_create</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(221, 0, 0);">'2008-11-11 17:00:00'</span><span style="color: rgb(0, 119, 0);">,</span> <span style="color: rgb(0, 0, 187);">timezone_open</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(221, 0, 0);">'America/Chicago'</span><span style="color: rgb(0, 119, 0);">));<br />
<br /></span><span style="color: rgb(255, 128, 0);">// Find out what time it is in New York.<br /></span><span style="color: rgb(0, 0, 187);">date_timezone_set</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(0, 0, 187);">$date</span><span style="color: rgb(0, 119, 0);">,</span> <span style="color: rgb(0, 0, 187);">timezone_open</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(221, 0, 0);">'America/New_York'</span><span style="color: rgb(0, 119, 0);">));<br />
<br /></span><span style="color: rgb(255, 128, 0);">// Now move it ahead to the following Tuesday.<br /></span><span style="color: rgb(0, 0, 187);">date_modify</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(0, 0, 187);">$date</span><span style="color: rgb(0, 119, 0);">,</span> <span style="color: rgb(221, 0, 0);">'+1 Tuesday'</span><span style="color: rgb(0, 119, 0);">);<br />
<br /></span><span style="color: rgb(255, 128, 0);">// Print out the result.<br /></span><span style="color: rgb(0, 119, 0);">print</span> <span style="color: rgb(0, 0, 187);">date_format</span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(0, 0, 187);">$date</span><span style="color: rgb(0, 119, 0);">,</span> <span style="color: rgb(221, 0, 0);">'l r'</span><span style="color: rgb(0, 119, 0);">);
<br /></span><span style="color: rgb(255, 128, 0);">// "Tuesday Tue, 18 Nov 2008 18:00:00 -0500"
<br /></span><span style="color: rgb(0, 0, 187);">?&gt;</span></span><br />
</p>
<p>
The Date API provides a helper function, <span style="color: rgb(0, 0, 187);">date_make_date($string, $timezone, $type)</span>,
where $string is a unixtimestamp, an ISO date, or a string like YYYY-MM-DD HH:MM:SS,
$timezone is the name of the timezone this date is in, and $type is the type
of date it is (DATE_UNIX, DATE_ISO, or DATE_DATETIME). It creates and return
a date object set to the right date and timezone.
</p>

View file

@ -0,0 +1,59 @@
<p>The date field can be displayed, or formatted, several ways, some configurable in the date settings, others that are automatically provided as options.</p>
<p>You'll see these formatters as options on the 'Display fields' screen and when you add the fields to Views. You can also use these formatters in custom code by doing something like:</p>
<pre>
print content_format('field_date', $node->field_date[0], 'iso');
</pre>
<h3>Formatter settings</h3>
<p>In many places you can further control the way multiple value dates will be displayed with formatter settings. The formatter settings look like:</p>
<div class="form-item" id="">
<select name="" class="form-select" id="" ><option value="default" selected="selected">Default</option><option value="format_interval">As Time Ago</option><option value="long">Long</option><option value="medium">Medium</option><option value="short">Short</option><option value="date">Date only</option><option value="time">Time only</option><option value="hidden">&lt;Hidden&gt;</option></select>
</div>
<div class="form-item" id="">
<select name="" class="form-select" id="" ><option value="both" selected="selected">Display From and To dates</option><option value="value">Display From date only</option><option value="value2">Display To date only</option></select>
</div>
<div class="form-item" id="">
<span class="field-prefix">Show </span> <input type="text" maxlength="128" name="" id="" size="5" value="5" class="form-text" /> <span class="field-suffix"> value(s)</span>
</div>
<div class="form-item" id="">
<span class="field-prefix">starting from </span> <input type="text" maxlength="128" name="" id="" size="15" value="now" class="form-text" /> <span class="field-suffix"></span>
</div>
<div class="form-item" id="">
<span class="field-prefix">ending on </span> <input type="text" maxlength="128" name="" id="" size="15" value="now +2 weeks" class="form-text" /> <span class="field-suffix"></span>
</div>
<div class="form-item" id="">
<select name="" class="form-select" id="" ><option value="show" selected="selected">Display repeat rule</option><option value="hide">Hide repeat rule</option></select>
</div>
<p>The number of values should be a numeric value, or blank to show all dates that match your other options.
'Starting from' and 'Ending on' can be a fixed dates in ISO format (like 2009-03-03T10:30:00) or relative times like 'now' or 'now +1 day'.
</p>
<h3>Formats and Format Types</h3>
<p>Formatter options include:</p>
<dl>
<dt>default</dt>
<dd>The main way to display this date, configured in the Date settings, defaults to the site short format.</dd>
<dt>short</dt>
<dd>The site short format.</dd>
<dt>medium</dt>
<dd>The site medium format.</dd>
<dt>long</dt>
<dd>The site long format.</dd>
<dt>time</dt>
<dd>Display the date time only, using the time format in the default display.</dd>
<dt>time_timezone</dt>
<dd>Display the date time and timezone, using the time format in the default display.</dd>
<dt>iso</dt>
<dd>Display the date in the standard ISO format of YYYY-MM-DDTHH:MM:SS.</dd>
<dt>ical</dt>
<dd>Display the date in the standard iCal format of YYYYMMDDTHHMMSS.</dd>
<dt>timestamp</dt>
<dd>Display the date as the Unix timestamp value.</dd>
<dt>format_interval</dt>
<dd>Display the date as 'XX days, XX hours ago' or 'XX months, XX days from now'.</dd>
</dl>
<p> To set up custom formats and formatters, go to <a href="&base_url&admin/settings/date-time/formats">admin/settings/date-time/formats</a>. You can either use a pre-configured display format, or create your own formatters and formats. Define a php date format string like 'm-d-Y H:i' (see <a href="http://php.net/date">http://php.net/date</a> for more details on custom format strings.)</p>

View file

@ -0,0 +1 @@
<p>The Date API provides a generic Date filter that can filter on any Views date field.</p>

View file

@ -0,0 +1,41 @@
<p><strong>Note:</strong> The Date API is designed to use the new PHP date and timezone functions that are available in PHP 5.2 and above. If you're using an earlier version of PHP, you'll need to enable the Date PHP4 module which provides some wrapper code to emulate those functions. The wrapper code is slower and less efficient than the native date functions, so if at all possible, you will want to run on a server that uses PHP 5.2 or higher for the best performance. Some distros, like Red Hat, use PHP 5.1 instead. See <a href="http://bluhaloit.wordpress.com/2008/03/13/installing-php-52x-on-redhat-es5-centos-5-etc/">Installing PHP 5.2 on RedHat</a> for ideas on how to update that.</p>
<p>PHP 4 substitutions for the PHP 5 date functions are supplied if the Date PHP4 module is enabled. Use the PHP 5
functions in your code as they would normally be used and the PHP 4
alternatives will be automatically be substituted in when needed.
</p>
<p>
You cannot do everything with these functions that can be done in PHP 5, but
you can create dates, find timezone offsets, and format the results.
Timezone handling uses native PHP 5 functions when available and degrades
automatically for PHP 4 to use substitutions like those
provided in previous versions of the Date and Event modules.
</p>
<p>
Read the doxygen documentation in this module for more information
about using the functions in ways that will work in PHP 4.
</p>
<p>
The following functions are emulated in PHP4:
</p>
<ul style="color: rgb(0, 0, 187);">
<li>
date_create()
</li><li>
date_date_set()
</li><li>
date_format()
</li><li>
date_offset_get()
</li><li>
date_timezone_set()
</li><li>
timezone_abbreviations_list()
</li><li>
timezone_identifiers_list()
</li><li>
timezone_offset_get()
</li><li>
timezone_open()
</li>
</ul>

View file

@ -0,0 +1,8 @@
<p>Dates will match any selection within the same box [January OR June]. When more than one box has criteria selected, all of them are combined to create repeats [[January OR June] AND [Day 1 OR Day 15]]. Positive numbers count from the beginning of the period. Negative numbers count from the end of the period, i.e. -1 is the last, -2 is the next to last, etc.</p>
<ul>
<li>If you select 'Every Year' above, and 'March' from 'Month' and '1' and '15' from 'Day of Month' in the Advanced options you will be selecting the 1st and 15th days of March of every year.</li>
<li>If you select 'Every other Month' above, and 'Second Tuesday' in the Advanced options you will be selecting the second Tuesday of every other month.</li>
<li>If you select 'Every Year' above, and 'Second Tuesday' in the Advanced options you will be selecting the second Tuesday of every year.</li>
<li>If you select 'Every Month' above, and 'January' and 'June' and 'First Saturday' in the Advanced options, you will be selecting the first Saturday in January or June.</li>
<li>If you select 'Every Month' above, and '-1' from 'Day of Month' in the Advanced options you will be selecting the last day of every month.</li>
</ul>

View file

@ -0,0 +1,447 @@
<h2>Creating a timezone form item using Date API</h2>
<p>
This is simple. Just use the 'date_timezone' type, which is provided by date_api.module.
</p>
<pre>
&lt;?php
$form['timezone'] = array(
'#type' => 'date_timezone',
'#default_value' => $timezone,
'#description' => "Select a timezone if all the events will happen in the same place. Select that place's timezone",
);
?&gt;
</pre>
<h2>Converting to and from UTC</h2>
<p>
In the new system you just need to know the timezone name and either the UTC or local time. We store the UTC time and the timezone name in the database (in two separate fields). We retrieve that data and do:
</p>
<pre>
&lt;?php
$date = the UTC value from the database
$local_zone = the name of the local timezone
$type = the type of date value, DATE_DATETIME, DATE_ISO, or DATE_UNIX
$date = date_make_date($date, 'UTC', $type);
date_timezone_set($date, timezone_open($local_zone);
$date now contains the local value of the date.
Output it like:
print date_format_date($date, 'custom', 'm/d/Y H:i');
?&gt;
</pre>
<p>
To convert a local date back to UTC to store it in the database, do:
</p>
<pre>
&lt;?php
$date = local date value
$local_zone = the name of the local timezone
$type = the type of date value, DATE_DATETIME, DATE_ISO, or DATE_UNIX
$date = date_make_date($date, $local_zone, $type);
date_timezone_set($date, timezone_open('UTC');
$date now contains the UTC value of the date.
Output it like:
print date_format_date($date, 'custom', 'm/d/Y H:i');
?&gt;
</pre>
<h2>Use of timezone abbreviations</h2>
<p>
The API doesn't handle <a href="http://www.timeanddate.com/library/abbreviations/timezones/">timezone abbreviations</a> at all, there's no way to do it right because they are not unique (EST could be Eastern Standard Time or European Summer Time). We need the full, undeprecated timezone name, which looks like 'America/Chicago'. It is comprised of the name of the continent and the major city in the timezone. Older names like 'US/Central' are deprecated because the area names change, but city names rarely do. Some good info about timezones is at http://en.wikipedia.org/wiki/Zoneinfo.
</p><p>
Also, we do not use spaces in the timezone names, so we use America/New_York, not America/New York. The PHP 5.2 date functions and MYSQL native timezone handling all expect names without spaces in them to work correctly.
</p><p>
You can use the <a href="http://php.net/manual/en/function.timezone-name-from-abbr.php">php function <code>timezone_name_from_abbr()</code></a> to try to translate to and from abbreviations, but it's not entirely reliable, as noted above. If we have the current offset and we know if it's currently daylight savings time or not, you can combine that with the abbreviation to get the timezone name, but that's a lot of 'if's.
</p>
<h2>Timezone Names</h2>
<p>
The complete list of allowed, undeprecated timezone names is:
</p>
<pre>
Africa/Algiers
Africa/Asmera
Africa/Bangui
Africa/Blantyre
Africa/Brazzaville
Africa/Bujumbura
Africa/Cairo
Africa/Ceuta
Africa/Dar_es_Salaam
Africa/Djibouti
Africa/Douala
Africa/Gaborone
Africa/Harare
Africa/Johannesburg
Africa/Kampala
Africa/Khartoum
Africa/Kigali
Africa/Kinshasa
Africa/Lagos
Africa/Libreville
Africa/Luanda
Africa/Lubumbashi
Africa/Lusaka
Africa/Malabo
Africa/Maputo
Africa/Maseru
Africa/Mbabane
Africa/Mogadishu
Africa/Nairobi
Africa/Ndjamena
Africa/Niamey
Africa/Porto-Novo
Africa/Tripoli
Africa/Tunis
Africa/Windhoek
America/Adak
America/Anchorage
America/Anguilla
America/Antigua
America/Araguaina
America/Aruba
America/Asuncion
America/Atka
America/Barbados
America/Belem
America/Belize
America/Boa_Vista
America/Bogota
America/Boise
America/Buenos_Aires
America/Cambridge_Bay
America/Cancun
America/Caracas
America/Catamarca
America/Cayenne
America/Cayman
America/Chicago
America/Chihuahua
America/Cordoba
America/Costa_Rica
America/Cuiaba
America/Curacao
America/Dawson
America/Dawson_Creek
America/Denver
America/Detroit
America/Dominica
America/Edmonton
America/Eirunepe
America/El_Salvador
America/Ensenada
America/Fort_Wayne
America/Fortaleza
America/Glace_Bay
America/Godthab
America/Goose_Bay
America/Grand_Turk
America/Grenada
America/Guadeloupe
America/Guatemala
America/Guayaquil
America/Guyana
America/Halifax
America/Havana
America/Hermosillo
America/Indiana/Indianapolis
America/Indiana/Knox
America/Indiana/Marengo
America/Indiana/Vevay
America/Indianapolis
America/Inuvik
America/Iqaluit
America/Jamaica
America/Jujuy
America/Juneau
America/Kentucky/Louisville
America/Kentucky/Monticello
America/Knox_IN
America/La_Paz
America/Lima
America/Los_Angeles
America/Louisville
America/Maceio
America/Managua
America/Manaus
America/Martinique
America/Mazatlan
America/Mendoza
America/Menominee
America/Merida
America/Mexico_City
America/Miquelon
America/Monterrey
America/Montevideo
America/Montreal
America/Montserrat
America/Nassau
America/New_York
America/Nipigon
America/Nome
America/Noronha
America/Panama
America/Pangnirtung
America/Paramaribo
America/Phoenix
America/Port-au-Prince
America/Port_of_Spain
America/Porto_Acre
America/Porto_Velho
America/Puerto_Rico
America/Rainy_River
America/Rankin_Inlet
America/Recife
America/Regina
America/Rio_Branco
America/Rosario
America/Santiago
America/Santo_Domingo
America/Sao_Paulo
America/Scoresbysund
America/Shiprock
America/St_Johns
America/St_Kitts
America/St_Lucia
America/St_Thomas
America/St_Vincent
America/Swift_Current
America/Tegucigalpa
America/Thule
America/Thunder_Bay
America/Tijuana
America/Tortola
America/Vancouver
America/Virgin
America/Whitehorse
America/Winnipeg
America/Yakutat
America/Yellowknife
Antarctica/Casey
Antarctica/Davis
Antarctica/DumontDUrville
Antarctica/Mawson
Antarctica/McMurdo
Antarctica/Palmer
Antarctica/South_Pole
Antarctica/Syowa
Antarctica/Vostok
Arctic/Longyearbyen
Asia/Aden
Asia/Almaty
Asia/Amman
Asia/Anadyr
Asia/Aqtau
Asia/Aqtobe
Asia/Ashgabat
Asia/Ashkhabad
Asia/Baghdad
Asia/Bahrain
Asia/Baku
Asia/Bangkok
Asia/Beirut
Asia/Bishkek
Asia/Brunei
Asia/Calcutta
Asia/Chungking
Asia/Colombo
Asia/Dacca
Asia/Damascus
Asia/Dhaka
Asia/Dili
Asia/Dubai
Asia/Dushanbe
Asia/Gaza
Asia/Harbin
Asia/Hong_Kong
Asia/Hovd
Asia/Irkutsk
Asia/Istanbul
Asia/Jakarta
Asia/Jayapura
Asia/Jerusalem
Asia/Kabul
Asia/Kamchatka
Asia/Karachi
Asia/Kashgar
Asia/Katmandu
Asia/Krasnoyarsk
Asia/Kuala_Lumpur
Asia/Kuching
Asia/Kuwait
Asia/Macao
Asia/Magadan
Asia/Manila
Asia/Muscat
Asia/Nicosia
Asia/Novosibirsk
Asia/Omsk
Asia/Phnom_Penh
Asia/Pyongyang
Asia/Qatar
Asia/Rangoon
Asia/Riyadh
Asia/Riyadh87
Asia/Riyadh88
Asia/Riyadh89
Asia/Saigon
Asia/Samarkand
Asia/Seoul
Asia/Shanghai
Asia/Singapore
Asia/Taipei
Asia/Tashkent
Asia/Tbilisi
Asia/Tehran
Asia/Tel_Aviv
Asia/Thimbu
Asia/Thimphu
Asia/Tokyo
Asia/Ujung_Pandang
Asia/Ulaanbaatar
Asia/Ulan_Bator
Asia/Urumqi
Asia/Vientiane
Asia/Vladivostok
Asia/Yakutsk
Asia/Yekaterinburg
Asia/Yerevan
Atlantic/Azores
Atlantic/Bermuda
Atlantic/Canary
Atlantic/Cape_Verde
Atlantic/Faeroe
Atlantic/Jan_Mayen
Atlantic/Madeira
Atlantic/South_Georgia
Atlantic/Stanley
Australia/ACT
Australia/Adelaide
Australia/Brisbane
Australia/Broken_Hill
Australia/Canberra
Australia/Darwin
Australia/Hobart
Australia/LHI
Australia/Lindeman
Australia/Lord_Howe
Australia/Melbourne
Australia/NSW
Australia/North
Australia/Perth
Australia/Queensland
Australia/South
Australia/Sydney
Australia/Tasmania
Australia/Victoria
Australia/West
Australia/Yancowinna
Europe/Amsterdam
Europe/Andorra
Europe/Athens
Europe/Belfast
Europe/Belgrade
Europe/Berlin
Europe/Bratislava
Europe/Brussels
Europe/Bucharest
Europe/Budapest
Europe/Chisinau
Europe/Copenhagen
Europe/Dublin
Europe/Gibraltar
Europe/Helsinki
Europe/Istanbul
Europe/Kaliningrad
Europe/Kiev
Europe/Lisbon
Europe/Ljubljana
Europe/London
Europe/Luxembourg
Europe/Madrid
Europe/Malta
Europe/Minsk
Europe/Monaco
Europe/Moscow
Europe/Nicosia
Europe/Oslo
Europe/Paris
Europe/Prague
Europe/Riga
Europe/Rome
Europe/Samara
Europe/San_Marino
Europe/Sarajevo
Europe/Simferopol
Europe/Skopje
Europe/Sofia
Europe/Stockholm
Europe/Tallinn
Europe/Tirane
Europe/Tiraspol
Europe/Uzhgorod
Europe/Vaduz
Europe/Vatican
Europe/Vienna
Europe/Vilnius
Europe/Warsaw
Europe/Zagreb
Europe/Zaporozhye
Europe/Zurich
Indian/Antananarivo
Indian/Chagos
Indian/Christmas
Indian/Cocos
Indian/Comoro
Indian/Kerguelen
Indian/Mahe
Indian/Maldives
Indian/Mauritius
Indian/Mayotte
Indian/Reunion
Pacific/Apia
Pacific/Auckland
Pacific/Chatham
Pacific/Easter
Pacific/Efate
Pacific/Enderbury
Pacific/Fakaofo
Pacific/Fiji
Pacific/Funafuti
Pacific/Galapagos
Pacific/Gambier
Pacific/Guadalcanal
Pacific/Guam
Pacific/Honolulu
Pacific/Johnston
Pacific/Kiritimati
Pacific/Kosrae
Pacific/Kwajalein
Pacific/Majuro
Pacific/Marquesas
Pacific/Midway
Pacific/Nauru
Pacific/Niue
Pacific/Norfolk
Pacific/Noumea
Pacific/Pago_Pago
Pacific/Palau
Pacific/Pitcairn
Pacific/Ponape
Pacific/Port_Moresby
Pacific/Rarotonga
Pacific/Saipan
Pacific/Samoa
Pacific/Tahiti
Pacific/Tarawa
Pacific/Tongatapu
Pacific/Truk
Pacific/Wake
Pacific/Wallis
Pacific/Yap
Pacific/French_Polynesia-Marquesas_Islands
UTC
</pre>

View file

@ -0,0 +1,39 @@
<h3>Date Fields</h3>
<p>The CCK Date field uses the <a href="topic:date_api/overview">Date API</a> to create date fields and widgets.</p>
<p>You have a choice of creating an ISO date or a unix timestamp. If you are porting information from another application you may want to create a field using a type that matches the source data. Some advantages and disadvantages of each include:</p>
<dl>
<dt>Datestamp (Unix Timestamp)</dt>
<dd><ul>
<li>Stores the date as an integer.</li>
<li>Takes up less room in the database because it's smaller.</li>
<li>Often easier to use for date calculations because you can increase or decrease it just by adding or subtracting seconds.</li>
<li>It is the format used by php date functions.</li>
<li>It must be filled with a complete date -- year, month, day, hour, minute, second, so you sometimes have to arbitrarily set some of these values even if they are not applicable.</li>
</ul></dd>
<dt>Date (ISO Date)</dt>
<dd><ul>
<li>Stores the date in an ISO format (YYYY-MM-DDTHH:MM:SS).</li>
<li>The data is in a human-readable form.</li>
<li>You can use it for incomplete dates, i.e. only a year or only a year and a month, and pad the other values with zeros, so it does not appear to be any more precise than it really is.</li>
<li>It is a format that is internationally-recognized, and it is used as-is on many web sites and in many applications.</li>
</ul></dd>
<dt>Datetime</dt>
<dd><ul>
<li>Stores the date in the database's native date format (YYYY-MM-DD HH:MM:SS).</li>
<li>The data is in a human-readable form.</li>
<li>You can use it for incomplete dates, i.e. only a year or only a year and a month, and pad the other values with zeros, so it does not appear to be any more precise than it really is.</li>
<li>It can take advantage of the database's date handling functions without any conversion.</li>
</ul></dd>
</dl>
<h3>Date Widgets</h3>
<p>There are several widgets to choose from to control how users can enter data for this field.</p>
<dl>
<dt>Text Field</dt><dd>The Text Field date widget uses a custom format set in the field settings, like '31.12.2008'. As a fallback the widget will try to accept input allowed by the php <a href="http://www.php.net/manual/en/function.strtotime.php">strtotime function</a>. This allows the user to type a date in in many natural formats, like 'March 31, 1980'.
There are limitations to strtotime, so setting a custom format is more reliable. The strtotime function will assume date shortcuts are in American format (MM/DD/YY), and strtotime will not work for dates prior to 1970.
</dd>
<dt>Select List</dt><dd>The Select List date widget presents users with a drop-down list or textfield for each part of the date (year, month, day, hour, etc.). The whole selector is collapsed onto a single line using css (except for the timezone selector, if date-specific timezones are collected). The selector is highly configurable and you can set it up to use 5, 10, or 15 minute increments for minutes and seconds, or make some parts of the selector use textfields instead of drop-downs, useful to allow any year without creating a huge drop-down selector.</dd>
<dt>jQuery Pop-up Calendar</dt><dd>The Javascript Pop-up Calendar is offered as an input alternative if the Date Popup module is installed. The date is split into date and time fields, and the date field uses a jQuery popup calendar and the time field uses a jQuery timeselector widget that only allows valid time input.
</dl>
<p>If you have the Date Repeat module enabled, you will also see options for each of those widgets that include a repeat options selection form that will allow the user to choose values like 'Every week' or 'The last Tuesday'.</p>

View file

@ -0,0 +1,45 @@
<p>Custom modules and add their own dates to the list the <a href="&topic:date_api/date-filter&">Date API Views filter</a> and <a href="&topic:date_api/date-argument&">Date API Views argument</a> can handle. See the following example for the way that core dates have been added:</p>
<pre>
/**
* Implementation of hook_date_api_fields().
* on behalf of core fields.
*
* All modules that create custom fields that use the
* 'views_handler_field_date' handler can provide
* additional information here about the type of
* date they create so the date can be used by
* the Date API views date argument and date filter.
*/
function date_api_date_api_fields($field) {
$values = array(
// The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
'sql_type' => DATE_UNIX,
// Timezone handling options: 'none', 'site', 'date', 'utc'.
'tz_handling' => 'site',
// Needed only for dates that use 'date' tz_handling.
'timezone_field' => '',
// Needed only for dates that use 'date' tz_handling.
'offset_field' => '',
// Array of "table.field" values for related fields that should be
// loaded automatically in the Views SQL.
'related_fields' => array(),
// Granularity of this date field's db data.
'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
);
switch ($field) {
case 'users.created':
case 'users.access':
case 'users.login':
case 'node.created':
case 'node.changed':
case 'node_revisions.timestamp':
case 'files.timestamp':
case 'node_counter.timestamp':
case 'accesslog.timestamp':
case 'comments.timestamp':
case 'node_comment_statistics.last_comment_timestamp':
return $values;
}
}
</pre>

View file

@ -0,0 +1,2 @@
<p>The CCK Date field is one of the <a href="&topic:content/fields&">Content Construction Kit (CCK) Field modules</a>. It uses the <a href="topic:date_api/overview">Date API</a> to create date fields and widgets.</p>
<p>There are a number of <a href="&topic:date_api/date-types&">Date fields and widgets</a> you can create with this module. </p>

View file

@ -0,0 +1,90 @@
[advanced help settings]
name = Date API
index name = "Date API"
[overview]
title = Overview
[date-api]
title = Date API
parent = overview
weight = 20
[new-features]
title = New Features
parent = overview
weight = -10
[date]
title = Date field
parent = overview
[date-php4]
title = Date PHP4
parent = overview
weight = 2
[date-types]
title = Date field
parent = content%fields
weight = -10
[date-display]
title = Date formatter display
parent = date
weight = 1
[date-copy]
title = Date Copy
parent = overview
weight = 1
[date-views]
title = Views Integration
parent = overview
weight = -5
[date-argument]
title = Generic date argument
parent = date-views
weight = 2
[date-filter]
title = Generic date filter
parent = date-views
weight = 3
[embedding]
title = Embedding Views
parent = date-views
weight = 4
[date-repeat-form]
title = Repeating date options
parent = date
[date-create]
title = The PHP DateTime Object
parent = date-api
weight = 1
[date-api-functions]
title = Date API Functions
parent = date-api
weight = 3
[form-elements]
title = Form Elements
parent = date-api
weight = 4
[date-timezones]
title = Timezone Handling
parent = date-api
weight = 5
[date-views-dates]
title = Views Date Handling
parent = date-api
weight = 7

View file

@ -0,0 +1,16 @@
<h3>Embedding Views</h3>
<p>Embed a date browser view anywhere using the format:</p>
<pre>date_embed_view($view_name, $display_name, $settings, $args)</pre>
<p>Settings are used to override some values of the view.</p>
<p>The primary setting that is useful here is a setting for 'block_identifier'. The view's block_identifier is used to control individual instances of regular and embedded views that contain date navigation so that they move separately or together. All views that use the same identifier will move together.
All views that use different identifiers will move independently. The identifier will show up in the url as a querystring, like node/275?mini=calendar/2008-10.
</p>
<h3>Examples:</h3>
<dl>
<dt>&lt;?php print date_embed_view('myview', 'page_1'); ?&gt;</dt>
<dd>This will embed the first page display in the view 'myview'.</dd>
<dt>&lt;?php print date_embed_view('myview', 'page_1', array('block_identifer' => 'embedded'), array('2008-W45')); ?&gt;</dt>
<dd>This will embed the first page display of the view and use 'embedded' as an identifier, set to show the week view for week 45 of 2008.</dd>
<dt>&lt;?php print date_embed_view('myview', 'block_1', array('block_identifier' => 'mini'), array('2008-07')); ?&gt;</dt>
<dd>This will embed the first block view of the view and use 'mini' as an identifier, set to show the month view for July 2008.</dd>
</dl>

View file

@ -0,0 +1,50 @@
<h3>Usage example of date elements within forms</h3>
<p>
* Enable the Date API module (you might want to add a dependency in the .info file of your module).
* Date API uses <a href="http://api.drupal.org/api/function/hook_elements">hook_elements()</a> to create custom form types. You can check the file date_api_elements.inc for the list of form types available, and for other configuration options.
</p>
<p>
Adding a date form element is then easy.
<pre>
&lt;?php
/**
* Your form builder.
*/
function mymodule_form($form_state) {
$form = array();
// [...snip...] add many fields to your form
// Creating the date/time element starts here
// Provide a default date in the format YYYY-MM-DD HH:MM:SS.
$date = '2008-12-31 00:00:00';
// Provide a format using regular PHP format parts (see documentation on php.net).
// If you're using a date_select, the format will control the order of the date parts in the selector,
// rearrange them any way you like. Parts left out of the format will not be displayed to the user.
$format = 'Y-m-d H:i';
$form['date2'] = array(
'#type' => 'date_select', // types 'date_text' and 'date_timezone' are also supported. See .inc file.
'#title' => 'select a date',
'#default_value' => $date,
'#date_format' => $format,
'#date_label_position' => 'within', // See other available attributes and what they do in date_api_elements.inc
'#date_timezone' => 'America/Chicago', // Optional, if your date has a timezone other than the site timezone.
'#date_increment' => 15, // Optional, used by the date_select and date_popup elements to increment minutes and seconds.
'#date_year_range' => '-3:+3', // Optional, used to set the year range (back 3 years and forward 3 years is the default).
);
// [...snip...] more fields, including the 'submit' button.
return $form;
}
?&gt;
</pre>
<p>These are self-validating elements, you shouldn't need to do anything except include them in your form. You pass them parameters for the timezone, format, and a default value in the format YYYY-MM-DD HH:MM:SS, and it will convert your input into the format the element wants, split the date and time into different fields as needed, then accept and validate the user input and convert it back into a string in the same format you originally provided (YYYY-MM-DD HH:MM:SS). So you pass it a string and it will pass back a string by the time you get to your own validation function.
</p><p>
You are responsible for doing your own timezone conversion, the element uses the timezone just so it can create a date object with the right timezone for doing its formatting. So if you need to do timezone conversion, you pull your UTC date out of the database and convert it to a local date, pass that value to the Date element, then take what it returns and convert it back to UTC and store it in the database again.
</p><p>
If the Date Popup module is enabled, a date_popup element can be used. It works like the other elements, but splits the date and time into separate parts. You control whether it uses date or date and time by the format you supply in #date_format. If you don't have any time parts in the format, you won't get a time element. Note that the jquery widget (the 'date' part of the popup) can only accept a limited number of formats, things like Y-m-d or m/d/Y or d.m.Y.
</p>

View file

@ -0,0 +1,11 @@
<ul>
<li>The Date API now uses PHP 5.2 date functions for better date and timezone handling (and has PHP 4 emulations for those functions so the module will work in PHP4). Although the code will work with PHP 4, it is <strong>significantly</strong> faster and more efficient under PHP 5.2 or higher, so PHP 5.2 is highly recommended.</li>
<li>The database query handling has been completely re-worked to take advantage of database timezone handling when available, with a fallback to the older and less reliable method of using offsets to do timezone adjustments.</li>
<li>No more need for the adodb date library, historical dates earlier than 1970 and later than 2038 are handled without any external code.</li>
<li> iCal integration is greatly improved, an iCal parser can parse events, alarms, most types of dates and timezones, duration, repeat rules, and more.</lI>
<li>The Date API creates date elements that can be used by any module, including 'date_select', 'date_textfield', and 'date_timezone'.</li>
<li> A new Date Timezone module overrides the site and user timezone selectors to allow you to select a timezone name instead of a timezone offset, and that stored name is used to properly adjust date values. It also detects the user's timezone name automatically and updates the user record with that name.</li>
<li>A new Date Popup module creates a jQuery popup calendar date picker and time picker, and the element is available to other modules as a form type of 'date_popup'. </li>
<li>The Date module now has lots of new ways to define default values -- you can set a the default to 'blank', 'now', or 'relative', where relative is something like '+90 days'. The To date has a separate default value, which can be the same as the From date or do something different.</li>
<li>A Date Repeat API module has been added which can be used by any other module. The CCK Date module uses it to allow you to select 'repeating' as a type of multiple date, present the user with a form to select the repeat rules for their date, and then create all the multiple values that match those rules.</li>
</ul>

View file

@ -0,0 +1,2 @@
<p>The <a href="http://drupal.org/project/date">date module</a> defines a highly configurable date/time field type for the content module and a date API that can be used by other applications.
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

View file

@ -0,0 +1,266 @@
<?php
/**
* @file
* Defines date-related Views data and plugins:
*
* Date argument:
* A generic date argument that has an option to select one or more
* Views date fields to filter on, automatically adds them to the view,
* and then filters the view by the value of the selected field(s).
* The flexible argument will accept and evaluate most ISO date
* and period formats, like 2009-05-01, 2008-W25, P1W.
*
* Date filter:
* A generic date filter that has an option to select a
* Views date field to filter on, with a choice of a widget to use
* for the filter form and an option to set the default value to
* a set date or something like 'now +90 days'. If the operator is
* set to 'between' or 'not between' you can set a default value for
* both the from and to dates.
*
* Current date argument default
* Adds a default option to set the argument to the current date
* when the argument is empty.
*
* Date navigation attachment
* Navigation that can be attached to any display to create back/next
* links by date, requires the date argument and uses the current
* date argument default to set a starting point for the view.
*/
/**
* Implementation of hook_views_handlers() to register all of the basic handlers
* views uses.
*/
function date_api_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'date_api') .'/includes',
),
'handlers' => array(
'date_api_argument_handler' => array(
'parent' => 'views_handler_argument_date',
),
'date_api_filter_handler' => array(
'parent' => 'views_handler_filter_numeric',
),
),
);
}
/**
* Implementation of hook_views_plugins().
*/
function date_api_views_plugins() {
$path = drupal_get_path('module', 'date_api');
$views_path = drupal_get_path('module', 'views');
require_once "./$path/theme/theme.inc";
return array(
'module' => 'date_api', // This just tells our themes are elsewhere.
'display' => array(
// Parents are not really displays, just needed so the files can
// be included.
'parent' => array(
'no ui' => TRUE,
'handler' => 'views_plugin_display',
'path' => "$views_path/plugins",
'parent' => '',
),
'attachment' => array(
'no ui' => TRUE,
'handler' => 'views_plugin_display_attachment',
'path' => "$views_path/plugins",
'parent' => 'parent',
),
// Display plugin for date navigation.
'date_nav' => array(
'title' => t('Date browser'),
'help' => t('Date back/next navigation to attach to other displays. Requires the Date argument.'),
'handler' => 'date_plugin_display_attachment',
'parent' => 'attachment',
'path' => "$path/includes",
'theme' => 'views_view',
'use ajax' => TRUE,
'admin' => t('Date browser'),
'help topic' => 'date-browser',
),
),
'style' => array(
'parent' => array(
// this isn't really a display but is necessary so the file can
// be included.
'no ui' => TRUE,
'handler' => 'views_plugin_style',
'path' => "$views_path/plugins",
'theme file' => 'theme.inc',
'theme path' => "$views_path/theme",
'parent' => '',
),
'date_nav' => array(
'title' => t('Date browser style'),
'help' => t('Creates back/next navigation.'),
'handler' => 'date_navigation_plugin_style',
'path' => "$path/includes",
'parent' => 'parent',
'theme' => 'date_navigation',
'theme file' => 'theme.inc',
'theme path' => "$path/theme",
'uses row plugin' => FALSE,
'uses fields' => FALSE,
'uses options' => TRUE,
'type' => 'date_nav',
'even empty' => TRUE,
),
),
);
}
/**
* Implementation of hook_views_data().
*/
function date_api_views_data() {
$data = array();
$tables = module_invoke_all('date_api_tables');
foreach ($tables as $base_table) {
// The flexible date argument.
$data[$base_table]['date_argument'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc).', array('!base_table' => $base_table)),
'argument' => array(
'handler' => 'date_api_argument_handler',
'empty field name' => t('Undated'),
'base' => $base_table,
),
);
// The flexible date filter.
$data[$base_table]['date_filter'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field.', array('!base_table' => $base_table)),
'filter' => array(
'handler' => 'date_api_filter_handler',
'empty field name' => t('Undated'),
'base' => $base_table,
),
);
}
return $data;
}
/**
* Identify all potential date/timestamp fields and cache the data.
*/
function date_api_fields($base = 'node', $reset = FALSE) {
static $fields = array();
$empty = array('name' => array(), 'alias' => array());
require_once('./'. drupal_get_path('module', 'date_api') .'/includes/date_api_fields.inc');
if (empty($fields[$base]) || $reset) {
$cid = 'date_api_fields_'. $base;
if (!$reset && $cached = cache_get($cid, 'cache_views')) {
$fields[$base] = $cached->data;
}
else {
$fields[$base] = _date_api_fields($base);
}
}
// Make sure that empty values will be arrays in he expected format.
return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
}
/**
* Central function for setting up the right timezone values
* in the SQL date handler.
*
* The date handler will use this information to decide if the
* database value needs a timezone conversion.
*
* In Views, we will always be comparing to a local date value,
* so the goal is to convert the database value to the right
* value to compare to the local value.
*/
function date_views_set_timezone(&$date_handler, &$view, $field) {
$tz_handling = $field['tz_handling'];
switch ($tz_handling) {
case 'date' :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone_field = $field['timezone_field'];
$date_handler->offset_field = $field['offset_field'];
break;
case 'none':
$date_handler->db_timezone = date_default_timezone_name();
$date_handler->local_timezone = date_default_timezone_name();
break;
case 'utc':
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = 'UTC';
break;
default :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = date_default_timezone_name();
break;
}
}
function date_views_querystring($view, $extra_params = array()) {
$query_params = array_merge($_GET, $extra_params);
// Allow NULL params to be removed from the query string.
foreach ($extra_params AS $key => $value) {
if (!isset($value)) {
unset($query_params[$key]);
}
}
// Filter the special "q" and "view" variables out of the query string.
$exclude = array('q');
// If we don't explicitly add a value for "view", filter it out.
if (empty($extra_params['view'])) {
$exclude[] = 'view';
}
$query = drupal_query_string_encode($query_params, $exclude);
// To prevent an empty query string from adding a "?" on to the end of a URL,
// we return NULL.
return !empty($query) ? $query : NULL;
}
/**
* Identify the base url of the page,
* needed when the calendar is embedded so we
* don't set the url to the calendar url.
*/
function date_views_page_url($view) {
if ($view->build_type == 'page') {
return date_views_real_url($view, $view->date_info->real_args);
}
else {
$block_identifier = isset($view->date_info->block_identifier) ? $view->date_info->block_identifier : 'mini';
return url($_GET['q'], date_views_querystring($view, array($block_identifier => NULL)), NULL, TRUE);
}
}
/**
* Figure out what the URL of the calendar view we're currently looking at is.
*/
function date_views_real_url($view, $args) {
if (empty($args)) {
return $view->date_info->url;
}
// Add non-calendar arguments to the base url.
$parts = explode('/', $view->date_info->url);
$bump = 0;
foreach ($parts as $delta => $part) {
// If one of the args is buried in the url, add it here and adjust
// the delta values we'll compare the calendar arg positions to.
if (drupal_substr($part, 0, 1) == '$') {
$parts[$delta] = array_shift($args);
$bump++;
}
}
foreach ($args as $delta => $arg) {
if (!in_array($delta + $bump, calendar_arg_positions($view)) && !empty($arg)) {
array_push($parts, $arg);
}
}
return implode('/', $parts);
}

View file

@ -0,0 +1,98 @@
<?php
/**
* @file
* Default Views.
*/
function date_api_views_default_views() {
$view = new view;
$view->name = 'date_browser';
$view->description = 'Browse through nodes by year, month, day, or week. Date browser attachment adds back/next navigation to the top of the page.';
$view->tag = 'Date';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('arguments', array(
'date_argument' => array(
'default_action' => 'default',
'style_plugin' => 'default_summary',
'style_options' => array(),
'wildcard' => 'all',
'wildcard_substitution' => 'All',
'title' => '',
'default_argument_type' => 'date',
'default_argument' => '',
'validate_type' => 'none',
'validate_fail' => 'not found',
'date_fields' => array(
'node.changed' => 'node.changed',
),
'year_range' => '-3:+3',
'date_method' => 'OR',
'granularity' => 'month',
'id' => 'date_argument',
'table' => 'node',
'field' => 'date_argument',
'relationship' => 'none',
'default_argument_user' => 0,
'default_argument_fixed' => '',
'default_argument_php' => '',
'validate_argument_node_type' => array(),
'validate_argument_node_access' => 0,
'validate_argument_nid_type' => 'nid',
'validate_argument_vocabulary' => array(),
'validate_argument_type' => 'tid',
'validate_argument_php' => '',
'default_options_div_prefix' => '',
'validate_argument_signup_status' => 'any',
'validate_argument_signup_node_access' => 0,
'override' => array(
'button' => 'Override',
),
),
));
$handler->override_option('access', array(
'type' => 'none',
'role' => array(),
'perm' => '',
));
$handler->override_option('items_per_page', 0);
$handler->override_option('use_pager', '1');
$handler->override_option('row_plugin', 'node');
$handler->override_option('row_options', array(
'teaser' => 1,
'links' => 1,
'comments' => 0,
));
$handler = $view->new_display('page', 'Page', 'page');
$handler->override_option('path', 'date-browser');
$handler->override_option('menu', array(
'type' => 'none',
'title' => '',
'weight' => 0,
'name' => 'navigation',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => '',
'weight' => 0,
));
$handler = $view->new_display('date_nav', 'Date browser', 'date_nav_1');
$handler->override_option('style_plugin', 'date_nav');
$handler->override_option('row_plugin', 'fields');
$handler->override_option('row_options', array());
$handler->override_option('attachment_position', 'before');
$handler->override_option('inherit_arguments', TRUE);
$handler->override_option('inherit_exposed_filters', TRUE);
$handler->override_option('displays', array(
'page' => 'page',
'default' => 0,
));
$views[$view->name] = $view;
return $views;
}

View file

@ -0,0 +1,398 @@
<?php
/**
* @file
* Views argument handler.
*/
/**
* Date API argument handler.
*/
class date_api_argument_handler extends views_handler_argument_date {
var $offset = NULL;
function construct() {
parent::construct();
require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_sql.inc');
$this->date_handler = new date_sql_handler();
$this->date_handler->construct();
}
/**
* Get granularity and use it to create the formula and a format
* for the results.
*/
function init(&$view, $options) {
parent::init($view, $options);
// Identify the type of display we're using.
$this->display_handler = $view->display_handler->definition['handler'];
// Add a date handler to the display.
$date_handler = $this->date_handler;
$date_handler->granularity = $this->options['granularity'];
$this->format = $date_handler->views_formats($date_handler->granularity, 'display');
$this->sql_format = $date_handler->views_formats($date_handler->granularity, 'sql');
if (empty($this->view->date_info)) $this->view->date_info = new stdClass();
// Set the view range, do this only if not already set in case there are multiple date arguments.
if (empty($this->view->date_info->min_allowed_year)) {
$range = date_range_years($this->options['year_range']);
$this->view->date_info->min_allowed_year = !empty($range) && is_array($range) ? $range[0] : variable_get('min_allowed_year', 100);
$this->view->date_info->max_allowed_year = !empty($range) && is_array($range) ? $range[1] : variable_get('max_allowed_year', 4000);
}
if (empty($this->view->date_info->date_fields)) {
$this->view->date_info->date_fields = array();
}
$this->view->date_info->date_fields = array_merge($this->view->date_info->date_fields, $this->options['date_fields']);
}
/**
* Default value for the date_fields option.
*/
function option_definition() {
$options = parent::option_definition();
$options['date_fields'] = array('default' => array());
$options['year_range'] = array('default' => '-3:+3', 'export' => 'export_plugin');
$options['date_method'] = array('default' => 'OR', 'export' => 'export_plugin');
$options['granularity'] = array('default' => 'month', 'export' => 'export_plugin');
$options['default_argument_type'] = array('default' => 'date', 'export' => 'export_plugin');
return $options;
}
/**
* Make sure our custom options get exported.
* Handle the options we know about, pass the rest to the parent plugin.
*/
function export_plugin($indent, $prefix, $storage, $option, $definition, $parents) {
$output = '';
if (in_array($option, array('year_range', 'granularity', 'default_argument_type', 'date_method'))) {
$name = $this->options[$option];
$output .= $indent . $prefix . "['$option'] = '$name';\n";
return $output;
}
return parent::export_plugin($indent, $prefix, $storage, $option, $definition, $parents);
}
/**
* Add a form element to select date_fields for this argument.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$options = $this->date_handler->date_parts();
unset($options['second'], $options['minute']);
$options += array('week' => date_t('Week', 'datetime'));
$form['granularity'] = array(
'#title' => t('Granularity'),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $this->options['granularity'],
'#multiple' => TRUE,
'#description' => t("Select the type of date value to be used in defaults, summaries, and navigation. For example, a granularity of 'month' will set the default date to the current month, summarize by month in summary views, and link to the next and previous month when using date navigation."),
);
$form['year_range'] = array(
'#title' => t('Date year range'),
'#type' => 'textfield',
'#default_value' => $this->options['year_range'],
'#description' => t("Set the allowable minimum and maximum year range for this argument, either a -X:+X offset from the current year, like '-3:+3' or an absolute minimum and maximum year, like '2005:2010'. When the argument is set to a date outside the range, the page will be returned as 'Page not found (404)'."),
);
$fields = date_api_fields($this->definition['base']);
$options = array();
foreach ($fields['name'] as $name => $field) {
$options[$name] = $field['label'];
}
// If this argument was added as a CCK field argument and no other date field
// has been chosen, update the default with the right date.
if (empty($this->options['date_fields']) && $this->field != 'date_argument') {
$this->options['date_fields'] = array($this->table .'.'. $this->field);
}
$form['date_fields'] = array(
'#title' => t('Date field(s)'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->options['date_fields'],
'#multiple' => TRUE,
'#description' => t("Select one or more date fields to filter with this argument. Do not select both the 'From date' and 'To date' for CCK date fields, only one of them is needed."),
);
$form['date_method'] = array(
'#title' => t('Method'),
'#type' => 'radios',
'#options' => array('OR' => t('OR'), 'AND' => t('AND')),
'#default_value' => $this->options['date_method'],
'#description' => t('Method of handling multiple date fields in the same query. Return items that have any matching date field (date = field_1 OR field_2), or only those with matches in all selected date fields (date = field_1 AND field_2).'),
);
}
function options_validate($form, &$form_state) {
// It is very important to call the parent function here:
parent::options_validate($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$check_fields = array_filter($form_state['values']['options']['date_fields']);
if (empty($check_fields)) {
form_error($form['date_fields'], t('You must select at least one date field for this argument.'));
}
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
}
function options_submit($form, &$form_state) {
// It is very important to call the parent function here:
parent::options_submit($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$form_state['values']['options']['date_fields'] = array_filter($form_state['values']['options']['date_fields']);
}
}
// Update the summary values to show selected granularity.
function admin_summary() {
$fields = date_api_fields($this->definition['base']);
if (!empty($this->options['date_fields'])) {
$output = array();
foreach ($this->options['date_fields'] as $field) {
$output[] = $fields['name'][$field]['label'];
}
return implode('<br />'. $this->options['date_method'] .' ', $output);
}
else {
return parent::admin_summary();
}
}
/**
* Set the empty argument value to the current date,
* formatted appropriately for this argument.
*/
function get_default_argument($raw = FALSE) {
$granularity = $this->options['granularity'];
if (!$raw && $this->options['default_argument_type'] == 'date') {
if ($granularity == 'week') {
$now = date_now();
$week = date_week(date_format($now, 'Y-m-d'));
return date_format($now, 'Y') .'-W'. $week;
}
else {
return date($this->format(), time());
}
}
else {
return parent::get_default_argument($raw);
}
}
function format() {
if (!empty($this->options['granularity'])) {
$date_handler = new date_sql_handler();
return $date_handler->views_formats($this->options['granularity']);
}
else {
return !empty($this->options[$this->option_name]) ? $this->options[$this->option_name] : 'Y-m';
}
}
/**
* Provide a link to the next level of the view from the summary.
*/
function summary_name($data) {
$format = $this->date_handler->views_formats($this->options['granularity'], 'display');
$value = $data->{$this->name_alias};
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide the argument to use to link from the summary to the next level;
* this will be called once per row of a summary, and used as part of
* $view->get_url().
*
* @param $data
* The query results for the row.
*/
function summary_argument($data) {
$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$value = $data->{$this->name_alias};
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide a link to the next level of the view from the argument.
*/
function title() {
$format = $this->date_handler->views_formats($this->options['granularity'], 'display');
$range = $this->date_handler->arg_range($this->argument);
return date_format_date($range[0], 'custom', $format);
}
/**
* Create a summary query that matches the granularity.
*
* Needed or Views will do a groupby on the complete date instead
* of only the part of the date actually used in the argument.
*/
function summary_query() {
$this->get_query_fields();
// No way to do summaries on more than one field at a time.
if (count($this->query_fields) > 1) {
return;
}
// Cause query->ensure_table to perform the correct join.
$this->table = $this->query_fields[0]['field']['table_name'];
$this->ensure_my_table();
$field = $this->query_fields[0]['field'];
$date_handler = $this->query_fields[0]['date_handler'];
// Get the SQL format for this granularity, like Y-m,
// and use that as the grouping value.
$format = $date_handler->views_formats($this->options['granularity'], 'sql');
$this->formula = $date_handler->sql_format($format, $date_handler->sql_field($field['fullname']));
// Add the computed field.
$this->base_alias = $this->name_alias = $this->query->add_field(NULL, $this->formula, $field['query_name']);
return $this->summary_basics(FALSE);
}
function get_query_fields() {
$fields = date_api_fields($this->definition['base']);
$fields = $fields['name'];
$min_date = isset($this->min_date) ? $this->min_date : NULL;
$min_utc = isset($this->min_utc) ? $this->min_utc : NULL;
$max_date = isset($this->max_date) ? $this->max_date : NULL;
$max_utc = isset($this->max_utc) ? $this->max_utc : NULL;
$this->query_fields = array();
foreach ($this->options['date_fields'] as $delta => $name) {
if (array_key_exists($name, $fields) && $field = $fields[$name]) {
$date_handler = new date_sql_handler();
$date_handler->construct($field['sql_type'], date_default_timezone_name());
$date_handler->granularity = $this->options['granularity'];
date_views_set_timezone($date_handler, $this, $field);
$this->query_fields[] = array('field' => $field, 'date_handler' => $date_handler);
}
}
}
/**
* Make sure the date field is added to the query.
*
* Do this in pre_query() so it will get done even if the argument
* is the wildcard, since query() is skipped when the wildcard is used.
*/
function pre_query() {
// Unset invalid date values before the query runs.
if (!empty($this->view->args) && count($this->view->args) > $this->position) {
$argument = $this->view->args[$this->position];
$parts = $this->date_handler->arg_parts($argument);
if (empty($parts[0]['date']) && empty($parts[0]['period'])) {
unset($this->view->args[$this->position]);
}
}
}
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
function query() {
$block_identifier = date_block_identifier($this->view);
if (!empty($this->view->block_identifier) || isset($_GET[$block_identifier])) {
// Retrieve the block arguments in a way that will work for
// urls like user/%/calendar/2009-04.
if (!empty($_GET[$block_identifier])) {
$path_args = explode('/', $this->view->get_path());
$mini_args = explode('/', $_GET[$block_identifier]);
foreach ($path_args as $pos => $key) {
if ($path_args[$pos] != '%') {
unset($mini_args[$pos]);
}
}
// Get rid of gaps in the array caused by embedded args.
$mini_args = array_values($mini_args);
$this->view->args = $mini_args;
}
$i = 0;
foreach ($this->view->argument as $argument) {
if ($argument->field == 'date_argument') {
$this->argument = $this->view->args[$argument->position];
break;
}
$i++;
}
}
$parts = $this->date_handler->arg_parts($this->argument);
foreach ($parts as $type) {
foreach ($type as $part) {
foreach ($part as $key => $value) {
if (!empty($value)) {
// The last part evaluated is the one that will 'stick'
// as the date type.
$this->granularity = $key;
$this->$key = $value;
}
}
}
}
$range = $this->date_handler->arg_range($this->argument);
$min_date = $range[0];
$max_date = $range[1];
$this->min_date = $min_date;
$this->max_date = $max_date;
// See if we're outside the allowed date range for our argument.
if (date_format($min_date, 'Y') < $this->view->date_info->min_allowed_year || date_format($max_date, 'Y') > $this->view->date_info->max_allowed_year) {
$this->forbid = TRUE;
$this->query->add_where('date', "0=1");
return;
}
// The second option seems to work better in the block view if
// set to something other than the original value.
// Need to keep an eye on this to be sure nothing else breaks.
//$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$format = $this->date_handler->views_formats($this->granularity, 'sql');
$this->get_query_fields();
if (!empty($this->query_fields)) {
// Use set_where_group() with the selected date_method
// of 'AND' or 'OR' to create the where clause.
$this->query->set_where_group($this->options['date_method'], 'date');
foreach ($this->query_fields as $query_field) {
$field = $query_field['field'];
if ($field['table_name'] != $this->table || !empty($this->relationship)) {
$this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship);
}
$date_handler = $query_field['date_handler'];
$table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name'];
$from_field = str_replace($field['table_name'] .'_', $table_alias .'.', $field['fromto'][0]);
$to_field = str_replace($field['table_name'] .'_', $table_alias .'.', $field['fromto'][1]);
if ($this->granularity != 'week') {
$from = $date_handler->sql_where_format($format, $from_field, '<=', date_format($max_date, $format));
$to = $date_handler->sql_where_format($format, $to_field, '>=', date_format($min_date, $format));
}
else {
$format = DATE_FORMAT_DATETIME;
$from = $date_handler->sql_where_date('DATE', $from_field, '<=', date_format($max_date, $format));
$to = $date_handler->sql_where_date('DATE', $to_field, '>=', date_format($min_date, $format));
}
$sql = str_replace('***table***', $field['table_name'], "($from AND $to)");
if ($sql) {
$this->query->add_where('date', $sql);
}
}
}
}
}

View file

@ -0,0 +1,179 @@
<?php
/**
* @file
* Identification for fields the Date handlers can use.
*/
/**
* Identify all potential date/timestamp fields.
*
* @return
* array with fieldname, type, and table.
* @see date_api_date_api_fields() which implements
* the hook_date_api_fields() for the core date fields.
*/
function _date_api_fields($base = 'node') {
// Make sure $base is never empty.
if (empty($base)) {
$base = 'node';
}
$cid = 'date_api_fields_'. $base;
cache_clear_all($cid, 'cache_views');
$all_fields = date_api_views_fetch_fields($base, 'field');
$fields = array();
foreach ((array) $all_fields as $name => $val) {
$fromto = array();
$tmp = explode('.', $name);
$field_name = $tmp[1];
$table_name = $tmp[0];
$alias = str_replace('.', '_', $name);
if (!$handler = views_get_handler($table_name, $field_name, 'field')) {
continue;
}
$handler_name = $handler->definition['handler'];
$type = '';
// For cck fields, get the date type.
$custom = array();
if (isset($handler->content_field)) {
if ($handler->content_field['type'] == 'date') {
$type = 'cck_string';
}
elseif ($handler->content_field['type'] == 'datestamp') {
$type = 'cck_timestamp';
}
elseif ($handler->content_field['type'] == 'datetime') {
$type = 'cck_datetime';
}
}
// Allow custom modules to provide date fields.
// The is_a() function makes this work for any handler
// that was derived from 'views_handler_field_date'.
// Unfortunately is_a() is deprecated in PHP 5.2, so we need
// a more convoluted test.
elseif ((version_compare(PHP_VERSION, '5.2', '<') && is_a($handler, 'views_handler_field_date')) || ($handler instanceof views_handler_field_date)) {
foreach (module_implements('date_api_fields') as $module) {
$function = $module .'_date_api_fields';
if ($custom = $function("$table_name.$field_name")) {
$type = 'custom';
break;
}
}
}
// Don't do anything if this is not a date field we can handle.
if (!empty($type)) {
// Handling for simple timestamp fields
$fromto = array($alias, $alias);
$tz_handling = 'site';
$related_fields = array();
$timezone_field = '';
$offset_field = '';
$rrule_field = '';
$delta_field = '';
$granularity = array('year', 'month', 'day', 'hour', 'minute');
// Handling for content field dates
if (isset($handler->content_field['tz_handling'])) {
$tz_handling = $handler->content_field['tz_handling'];
$db_info = content_database_info($handler->content_field);
if ($tz_handling == 'date') {
$offset_field = $table_name .'.'. $db_info['columns']['offset']['column'];
}
$related_fields = array(
$table_name .'.'. $field_name
);
if (isset($db_info['columns']['value2']['column'])) {
$related_fields = array_merge($related_fields, array($table_name .'.'. $db_info['columns']['value2']['column']));
}
if (isset($db_info['columns']['timezone']['column'])) {
$related_fields = array_merge($related_fields, array($table_name .'.'. $db_info['columns']['timezone']['column']));
$timezone_field = $table_name .'.'. $db_info['columns']['timezone']['column'];
}
if (isset($db_info['columns']['rrule']['column'])) {
$related_fields = array_merge($related_fields, array($table_name .'.'. $db_info['columns']['rrule']['column']));
$rrule_field = $table_name .'.'. $db_info['columns']['rrule']['column'];
}
}
// Get the delta value into the query.
if (!empty($handler->content_field['multiple'])) {
array_push($related_fields, "$table_name.delta");
$delta_field = $table_name .'_delta';
}
// Handling for cck fromto dates
if (isset($handler->content_field)) {
switch ($handler->content_field['type']) {
case 'date':
case 'datetime':
case 'datestamp':
$db_info = content_database_info($handler->content_field);
$fromto = array(
$table_name .'_'. $db_info['columns']['value']['column'],
$table_name .'_'. (!empty($handler->content_field['todate']) ? $db_info['columns']['value2']['column'] : $db_info['columns']['value']['column']),
);
break;
}
$granularity = !empty($handler->content_field['granularity']) ? $handler->content_field['granularity'] : array('year', 'month', 'day', 'hour', 'minute');
}
// CCK fields append a column name to the field, others do not
// need a real field_name with no column name appended for cck date formatters
switch ($type) {
case 'cck_string':
$sql_type = DATE_ISO;
break;
case 'cck_datetime':
$sql_type = DATE_DATETIME;
break;
default:
$sql_type = DATE_UNIX;
break;
}
$fields['name'][$name] = array(
'type' => $type,
'sql_type' => $sql_type,
'label' => $val['group'] .': '. $val['title'],
'granularity' => $granularity,
'fullname' => $name,
'table_name' => $table_name,
'field_name' => $field_name,
'query_name' => $alias,
'fromto' => $fromto,
'tz_handling' => $tz_handling,
'offset_field' => $offset_field,
'timezone_field' => $timezone_field,
'rrule_field' => $rrule_field,
'related_fields' => $related_fields,
'delta_field' => $delta_field,
);
// Allow the custom fields to over-write values.
if (!empty($custom)) {
foreach ($custom as $key => $val) {
$fields['name'][$name][$key] = $val;
}
}
if (isset($handler->content_field)) {
if (drupal_substr($field_name, -1) == '2') {
$len = (drupal_strlen($field_name) - 7);
}
else {
$len = (drupal_strlen($field_name) - 6);
}
$fields['name'][$name]['real_field_name'] = drupal_substr($field_name, 0, $len);
}
else {
$fields['name'][$name]['real_field_name'] = $field_name;
}
$fields['alias'][$alias] = $fields['name'][$name];
}
}
cache_set($cid, $fields, 'cache_views');
return $fields;
}

Some files were not shown because too many files have changed in this diff Show more