lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
1,632 lines (1,413 loc) • 50.2 kB
JavaScript
/**
* @DateTime.js
* @author xunxuzhang
* @version
* Created: 15-07-03
*/
(function (global, factory) {
if (typeof define === 'function' && (define.amd || define.cmd)) {
define(factory);
} else {
global.DateTime = factory();
}
}(this, function (require, exports, module) {
if (typeof require == 'function') {
require('common/ui/Follow');
} else if (!$().follow) {
if (window.console) {
console.error('need Follow.js');
}
return {};
}
/**
* 日期,时间选择器
* 基于HTML5时间类输入框
* new DateTime(trigger, options);
* @trigger 触发的元素,可以是文本框也可以是文本框容器(父级)
* @options 可选参数
*/
var prefixDate = 'ui-date-',
prefixRange = 'ui-range-',
prefixDay = 'ui-day-',
prefixYear = 'ui-year-',
prefixMonth = 'ui-month-',
prefixHour = 'ui-hour-',
prefixMinute = 'ui-minute-';
var SELECTED= 'selected', ACTIVE = 'active';
var regDate = /\-|\//g;
// 拓展方法之字符串变时间对象
String.prototype.toDate = function() {
var year, month, day;
var arrDate = this.split(regDate);
year = arrDate[0] * 1;
month = arrDate[1] || 1;
day = arrDate[2] || 1;
// 年份需要是数值字符串
if (!year) {
return new Date();
}
return new Date(year, month-1, day);
};
// 日期对象变成年月日数组
Date.prototype.toArray = function() {
var year = this.getFullYear(), month = this.getMonth() + 1, date = this.getDate();
if (month < 10) {
month = '0' + month;
}
if (date < 10) {
date = '0' + date;
}
return [year, month, date];
};
var DateTime = function(trigger, options) {
if (!trigger || !trigger.length) return this;
// 默认参数
var defaults = {
value: '', // 文本框初始值,优先从文本框获取。
type: 'auto', // 浮层面板类型,可以是'date'(2015-01-01), 'year'(2015), 'month'(2015-01), 'time'(12:00), 'date-range'(2015-01-01 至 2015-06-01)
min: 'auto', // 时间范围最小值,优先从文本框获取。'auto'表示无最小限制。
max: 'auto', // 时间范围最大值,优先从文本框获取。'auto'表示无最大限制。
trigger: ['change'], // 文本框值修改时候触发的文本框事件
onShow: $.noop,
onHide: $.noop
};
var params = $.extend({}, defaults, options || {});
// 找到文本输入框
var input = null;
if (trigger.get(0).type) {
input = trigger;
trigger = input.parent();
} else {
input = trigger.find('input');
}
// 如果没有时间类输入框,则byebye
if (input.length == 0) return this;
// readonly
input.prop('readonly', true);
// hover, active style
input.parent().hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
// 时间选择的类型
var type = params.type;
if (type == 'auto') {
type = input.attr('type') || 'date';
}
//if (type == 'date') {
// Chrome新版无法通过CSS修改连接符
// 因此重置type
//try {input.attr('type', '_date_')}catch(e){};
//}
// 插入下拉箭头
var id = input.attr('id');
if (!id) {
// 如果没有id, 创建随机id
// 下拉箭头需要
id = type + (Math.random() + '').replace('0.', '');
input.attr('id', id);
}
// 下拉元素
$('<label for="'+ id +'"></label>').addClass(prefixDate + 'arrow')
// 插入到文本框的后面
.insertAfter(input);
// 初始值
var initValue = input.val();
if (initValue == '' && params.value) {
input.val(params.value);
initValue = params.value;
}
// 初始值转换成时间值
switch (type) {
case 'date': case 'year': case 'month': {
// 日期
var initDate = initValue.toDate(), arrDate = initDate.toArray();
if (initValue == '') {
// 赋值今日
if (type == 'date') {
input.val(arrDate.join('-'));
} else if (type == 'year') {
input.val(arrDate[0]);
} else if (type == 'month') {
input.val(arrDate.slice(0, 2).join('-'));
}
}
this[SELECTED] = arrDate; // eg. [2015,07,20]
break;
}
case 'time': case 'hour': case 'minute': {
var arrTime = initValue.split(':');
// 时间
var hour = arrTime[0], minute = arrTime[1];
// 这里的处理情况如下
// 1. 空值
// 2. 小时数不是数值
// 3. 小时数不在0~24之间
// 以上均认为时间是00:00
if (initValue == '' || !(hour < 24 && hour > 0)) {
hour = '00';
minute = '00';
} else {
// 分钟无论如何都要是正常值
if (!(minute > 0 && minute < 60) || type == 'hour') {
minute = '00';
} else if (minute.length == 1) {
minute = '0' + minute;
}
// 补0是必要的
if (hour.length == 1) {
hour = '0' + hour;
}
}
input.val([hour, minute].join(':'));
this[SELECTED] = [hour, minute];
break;
}
case 'date-range': case 'month-range': {
// 日期范围
var beginDate = new Date(), endDate = new Date();
// 前后时间字符串
var arrRange = initValue.split(' ');
// 有如下一些情况:
// 1. 空,则选择范围就是今日
// 2. 只有一个时间,则选择范围只这个时间到今天这个范围
// 3. 其他就是正常的
if (initValue != '' && arrRange.length == 1) {
var someDate = arrRange[0].toDate();
if (someDate.getTime() > beginDate.getTime()) {
endDate = someDate;
} else {
beginDate = someDate;
}
} else {
beginDate = arrRange[0].toDate();
endDate = arrRange[arrRange.length - 1].toDate();
}
// 赋值
var arrBegin = beginDate.toArray(), arrEnd = endDate.toArray();
if (type == 'date-range') {
input.val(arrBegin.join('-') + ' 至 ' + arrEnd.join('-'));
} else {
input.val(arrBegin.slice(0,2).join('-') + ' 至 ' + arrEnd.slice(0,2).join('-'));
}
// 存储
this[SELECTED] = [arrBegin, arrEnd];
break;
}
}
var self = this;
var container = $('<div></div>').addClass(prefixDate + 'container').delegate('a', 'click', function() {
// 各个分支中可能会用到的变量
var year = 0, month = 0, date = 0, hour = 0;
// 各种事件
switch (container.attr('data-type')) {
case 'date': {
// 日期选择主体
// 1. 前后月份选择
if (/prev|next/.test(this.className)) {
month = $(this).attr('data-month');
self[SELECTED][1] = month * 1;
// 日期和月份要匹配,例如,不能出现4月31日
var monthDay = self._monthDay(self[SELECTED]);
var day = self[SELECTED][2],
dayOverflow = container.data('dayOverflow'),
dayMax = (function() {
if (month - 1 < 0) {
return monthDay[11];
} else if (month > monthDay.length) {
return monthDay[0];
}
return monthDay[month - 1];
})();
// 例如,我们超出日期是31日,如果月份可以满足31日,使用31日
if (dayOverflow) {
self[SELECTED][2] = Math.min(dayOverflow, dayMax);
} else if (self[SELECTED][2] > dayMax) {
self[SELECTED][2] = dayMax;
// 这里是对体验的升级,
// 虽然下月份变成了30号,但是再回来时候,原来的31号变成了30号
// 不完美,我们需要处理下
// 通过一个变量记住,点击item项的时候,移除
// 且只在第一次的时候记住
// 因为28,29,30,31都可能出现,每次记忆会混乱
container.data('dayOverflow', day);
}
self[SELECTED] = self[SELECTED].join('-').toDate().toArray();
// 刷新
self.date();
// 如果在时间范围内
if (container.find('.' + SELECTED).get(0).href) {
self.val();
}
} else if (/item/.test(this.className)) {
// 选择某日期啦
date = this.innerHTML;
if (/\D/.test(date)) {
// 今天
self[SELECTED] = new Date().toArray();
} else {
if (date < 10) date = '0' + date;
// 修改全局
self[SELECTED][2] = date;
}
// 赋值
self.val();
// 隐藏
self.hide();
container.removeData('dayOverflow');
} else if ($(this).attr('data-type') == 'month') {
// 切换到年份选择
self.month();
}
break;
}
case 'date-range': {
// 区域选择
// 1. 前后月份选择
if (/prev|next/.test(this.className)) {
month = $(this).attr('data-month') * 1;
var arrRange = self.el.container.data('date') || self[SELECTED][0];
// 跟其他面板不同,这里只刷新,点击确定再赋值
self.el.container.data('date', new Date(arrRange[0], month - 1, /*arrRange[2]*/1).toArray());
// 之前arrRange[2]存在跨多月风险,尤其31号的日子
// 刷新
self['date-range']();
} else if (/item/.test(this.className)) {
// 选择某日期
// 获得选中的年月日
year = $(this).attr('data-year');
month = $(this).attr('data-month');
date = this.innerHTML;
if (month < 10) month = '0' + month;
if (date < 10) date = '0' + date;
// 根据选中状态决定新的状态
var range = self[SELECTED];
if (range[0].join() == range[1].join()) {
// 如果之前前后日期一样,说明只选中了一个日期
// 根据前后顺序改变其中一个日期
if (year + month + date > range[0].join('')) {
// 新时间靠后
range[1] = [year, month, date];
} else {
range[0] = [year, month, date];
}
} else {
// 如果前后时间不一样,说明现在有范围
// 则取消范围,变成单选
range = [[year, month, date], [year, month, date]];
}
self[SELECTED] = range;
self['date-range']();
} else if (/button/.test(this.className)) {
var typeButton = $(this).attr('data-type');
if (typeButton == 'ensure') {
// 点击确定按钮
// 赋值
self.val();
// 修改存储值
self._rangeSelected = self[SELECTED];
// 关闭浮层
self.hide();
} else if (typeButton == 'cancel') {
// 重置选中值
if (self._rangeSelected) {
self[SELECTED] = self._rangeSelected;
}
// 关闭浮层
self.hide();
}
}
break;
}
case 'month-range': {
// 区域选择
// 1. 前后年份选择
if (/prev|next/.test(this.className)) {
year = $(this).attr('data-year') * 1;
var arrRange = self.el.container.data('date') || self[SELECTED][0];
// 跟其他面板不同,这里只刷新,点击确定再赋值
self.el.container.data('date', new Date(year, arrRange[1], 1).toArray());
// 刷新
self['month-range']();
} else if (/item/.test(this.className)) {
// 选择某日期
// 获得选中的年月日
year = $(this).attr('data-year');
month = $(this).attr('data-value');
date = '01';
// 根据选中状态决定新的状态
var range = self[SELECTED];
if (range[0].join() == range[1].join()) {
// 如果之前前后日期一样,说明只选中了一个日期
// 根据前后顺序改变其中一个日期
if (year + month + date > range[0].join('')) {
// 新时间靠后
range[1] = [year, month, date];
} else {
range[0] = [year, month, date];
}
} else {
// 如果前后时间不一样,说明现在有范围
// 则取消范围,变成单选
range = [[year, month, date], [year, month, date]];
}
self[SELECTED] = range;
self['month-range']();
} else if (/button/.test(this.className)) {
var typeButton = $(this).attr('data-type');
if (typeButton == 'ensure') {
// 点击确定按钮
// 赋值
self.val();
// 修改存储值
self._rangeSelected = self[SELECTED];
// 关闭浮层
self.hide();
} else if (typeButton == 'cancel') {
// 重置选中值
if (self._rangeSelected) {
self[SELECTED] = self._rangeSelected;
}
// 关闭浮层
self.hide();
}
}
break;
}
case 'month': {
// 选择月份,可能从年份,也可能从日期过来
// 1. 前后年份
if (/prev|next/.test(this.className)) {
year = $(this).attr('data-year');
// 修改当前选中的年份数
self[SELECTED][0] = year * 1;
// 刷新
self.month();
// 文本框赋值
// 如果在区域内状态
if (container.find('.' + SELECTED).get(0).href) {
self.val();
}
} else if (/item/.test(this.className)) {
// value实际上是月份两位数值
var value = $(this).attr('data-value');
if (value) {
self[SELECTED][1] = value;
} else {
// 今月,只改变年月为今年和今月
var arrToday = new Date().toArray();
self[SELECTED][0] = arrToday[0];
self[SELECTED][1] = arrToday[1];
}
// 赋值
self.val();
// 根据是否是月份输入框,决定是面板切换,还是关闭
if (self.type == 'month') {
// 隐藏
self.hide();
} else {
self.date();
}
} else if ($(this).attr('data-type') == 'year') {
// 切换到年份选择
self.year();
}
break;
}
case 'year': {
// 选择年份,可能从月份过来,也可能直接打开
// 1. 前后12年翻页
if (/prev|next/.test(this.className)) {
year = $(this).attr('data-year')
// 修改当前选中的年份数
self[SELECTED][0] = year * 1;
// 刷新
self.year();
// 文本框赋值
// 如果在区域内状态
if (container.find('.' + SELECTED).get(0).href) {
self.val();
}
} else if (/item/.test(this.className)) {
if (this.innerHTML == '今年') {
self[SELECTED][0] = new Date().getFullYear();
} else {
self[SELECTED][0] = this.innerHTML * 1;
}
// 赋值
self.val();
// 如果是年份选择输入框
if (self.type == 'year') {
// 隐藏
self.hide();
} else {
// 回到月份面板
self.month();
}
}
break;
}
case 'minute': {
// 选择分钟,可以是minute类型,或者time类型, 但不包括hour类型
// 1. 前后翻页
if (/prev|next/.test(this.className)) {
hour = $(this).attr('data-hour')
if (hour.length == 1) {
hour = '0' + hour;
}
// 修改当前选中的小时数
self[SELECTED][0] = hour;
// 刷新
self.minute();
// 文本框赋值
// 如果在区域内状态
if (container.find('.' + SELECTED).attr('href')) {
self.val();
}
} else if (/item/.test(this.className)) {
// 确定选择时间
self[SELECTED] = this.innerHTML.split(':');
self.val();
self.hide();
} else if ($(this).attr('data-type') == 'hour') {
// 切换到小时选择
self.hour();
}
break;
}
case 'hour': {
if (/item/.test(this.className)) {
// 修改选中的小时
self[SELECTED][0] = this.innerHTML.split(':')[0];
// 赋值
self.val();
// 如果是从分钟模式切换过来,则切换回去,否则,直接隐藏
if (self.type == 'hour') {
self.hide();
} else {
self.minute();
}
}
}
}
});
this.el = {};
this.el.container = container;
this.el.trigger = trigger;
this.el.input = input;
this.type = type;
// 最大小值的处理在各个种类的面板中执行
// 以便支持动态时间修改
// 例如,选择A时间,然后设置B时间的min值为A时间
this.max = params.max;
this.min = params.min;
// 回调方法
this.callback = {
show: params.onShow,
hide: params.onHide,
trigger: params.trigger
};
trigger.click($.proxy(function(event) {
if (!this.display) {
this.show();
} else {
this.hide();
}
event.preventDefault();
}, this));
// 时间范围选择点击页面空白区域不会隐藏
$(document).mouseup($.proxy(function(event) {
// 点击页面空白区域,隐藏
var target = event && event.target, container = this.el.container.get(0);
if (target &&
trigger.get(0) != target && trigger.get(0).contains(target) == false &&
container != target && container.contains(target) == false
) {
this.hide();
}
}, this));
// IE9+ 前后分页图标使用内联SVG, 以便支持retina屏幕
this.svg = window.addEventListener?
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><path d="M85.876,100.5l49.537-50.526c4.089-4.215,4.089-11.049,0-15.262 c-4.089-4.218-10.719-4.218-14.808,0L63.586,92.868c-4.089,4.215-4.089,11.049,0,15.264l57.018,58.156 c4.089,4.215,10.719,4.215,14.808,0c4.089-4.215,4.089-11.049,0-15.262L85.876,100.5z"/></svg>'
: '';
return this;
};
// 赋值
DateTime.prototype.format = function() {
// 根据当前value值修改实例对象缓存的选中值
// 特殊情况一般不处理
var type = this.type;
var initValue = this.el.input.val();
if (initValue == '') return this;
switch (type) {
case 'date': case 'year': case 'month': {
// 日期
var initDate = initValue.toDate(), arrDate = initDate.toArray();
this[SELECTED] = arrDate; // eg. [2015,07,20]
break;
}
case 'time': case 'hour': case 'minute': {
var arrTime = initValue.split(':');
// 时间
var hour = arrTime[0], minute = arrTime[1];
// 补0
if (arrTime.length == 2) {
// 分钟无论如何都要是正常值
if (!(minute > 0 && minute < 60) || type == 'hour') {
minute = '00';
} else if (minute.length == 1) {
minute = '0' + minute;
}
// 补0是必要的
if (hour.length == 1) {
hour = '0' + hour;
}
this.el.input.val([hour, minute].join(':'));
this[SELECTED] = [hour, minute];
}
break;
}
case 'date-range': case 'month-range': {
// 日期范围
var beginDate = new Date(), endDate = new Date();
// 前后时间字符串
var arrRange = initValue.split(' ');
if (arrRange.length == 3) {
beginDate = arrRange[0].toDate();
endDate = arrRange[arrRange.length - 1].toDate();
// 存储
this[SELECTED] = [beginDate.toArray(), endDate.toArray()];
}
break;
}
}
return this;
};
// 赋值
DateTime.prototype.val = function() {
var input = this.el.input, selected = this[SELECTED], value = input.val();
switch (this.type) {
case 'date': {
input.val(selected.join('-'));
break;
}
case 'month': {
input.val(selected.slice(0,2).join('-'));
break;
}
case 'year': {
input.val(selected[0]);
break;
}
case 'date-range': {
input.val(selected[0].join('-') + ' 至 ' + selected[1].join('-'));
break;
}
case 'month-range': {
input.val(selected[0].slice(0,2).join('-') + ' 至 ' + selected[1].slice(0,2).join('-'));
break;
}
case 'time': case 'minute': {
input.val(selected.join(':'));
break;
}
case 'hour': {
input.val(selected[0] + ':00');
break;
}
}
if (input.val() != value) {
if ($.isArray(this.callback.trigger)) {
$.each(this.callback.trigger, function(i, eventType) {
input.trigger(eventType);
});
} else {
input.trigger(this.callback.trigger);
}
}
return this;
};
// 返回日历HTML字符串等数据的私有方法
// 因date和range浮层主结构类似,因此这里公用下
DateTime.prototype._calendar = function(date) {
var html = '';
// 根据当前日期数据返回
// eg. [2015,'02', 23]
var arrDate = date;
// 文本框元素比较常用,使用局部变量,一是节约文件大小,二是性能更好!
var input = this.el.input;
// 类型
var type = this.type;
// 最大月份和最小月份
var min = input.attr('min') || this.min, max = input.attr('max') || this.max;
// 这里使用日期对象(时间戳)做比对
var arr = $.map([min, max], function(min_max, index) {
if (typeof min_max == 'string' && /^\d{8}$/.test(min_max.replace(regDate, '')) == true) {
// 这里对字符串进行比较简单的合法性判别
// 自己人使用不会乱来的
min_max = min_max.toDate();
} else if (typeof min_max != 'object' || !min_max.getTime) {
min_max = index? new Date(9999,0,1): new Date(0,0,1);
}
return min_max;
});
min = arr[0];
max = arr[1];
var chinese = ['日', '一', '二', '三', '四', '五', '六'];
var monthDay = this._monthDay(arrDate);
// 目前日期对象
var currentDate = arrDate.join('-').toDate();
// 3 星期几七大罪
html = html + '<div class="'+ prefixDay +'x">' + (function() {
var htmlDay = '';
$.each(chinese, function(index, value) {
htmlDay = htmlDay + '<span class="'+ prefixDay +'item">'+ value +'</span>';
});
return htmlDay;
})() +'</div>';
// 4. 日期详细
// 4.1 首先算出今月1号是星期几
var newDate = arrDate.join('-').toDate(), dayFirst = 0;
newDate.setDate(1);
if (newDate.getDate() == 2) {
newDate.setDate(0);
}
// 每月的第一天是星期几
dayFirst = newDate.getDay();
// 上个月是几月份
var lastMonth = newDate.getMonth() - 1;
if (lastMonth < 0) lastMonth = 11;
var htmlData = 'data-year="'+ arrDate[0] +'" data-month="'+ (newDate.getMonth() + 1) +'"';
html = html + '<div class="'+ prefixDate +'body">' + (function() {
var htmlDate = '', cl = '';
for (var tr=0; tr<6; tr++) {
htmlDate = htmlDate + '<div class="'+ prefixDate +'tr">';
for (var td=0; td<7; td++) {
// 类名
cl = prefixDate + 'item col' + td;
// 由于range选择和date选择UI上有比较大大差异
// 为了可读性以及后期维护
// 这里就不耦合在一起,而是分开处理
if (type == 'date') {
// 第一行上个月一些日期补全
if (tr == 0 && td < dayFirst) {
htmlDate = htmlDate + '<span class="'+ cl +'">'+ (monthDay[lastMonth] - dayFirst + td + 1) +'</span>';
} else {
var dayNow = tr * 7 + td - dayFirst + 1;
// 如果没有超过这个月末
if (dayNow <= monthDay[newDate.getMonth()]) {
// 这个日子对应的时间对象
var dateNow = new Date(arrDate[0], newDate.getMonth(), dayNow);
// 如果日子匹配
if (currentDate.getDate() == dayNow) {
cl = cl + ' ' + SELECTED;
}
// 如果在日期范围内
// 直接使用时间对象 Date 类作比较
if (dateNow >= min && dateNow <= max) {
htmlDate = htmlDate + '<a href="javascript:;" '+ htmlData +' class="'+ cl +'">'+ dayNow +'</a>';
} else {
htmlDate = htmlDate + '<span class="'+ cl +'">'+ dayNow +'</span>';
}
} else {
htmlDate = htmlDate + '<span class="'+ cl +'">'+ (dayNow - monthDay[newDate.getMonth()]) +'</span>';
}
}
} else if (type == 'date-range') {
// 非当前月部分使用空格补全
if (tr == 0 && td < dayFirst) {
htmlDate = htmlDate + '<span class="'+ cl +'"> </span>';
} else {
var dayNow = tr * 7 + td - dayFirst + 1;
// 如果没有超过这个月末
if (dayNow <= monthDay[newDate.getMonth()]) {
// 这个日子对应的时间对象
var dateNow = new Date(arrDate[0], newDate.getMonth(), dayNow);
// range选择的匹配规则如下:
// 1. 获得已经选中到时间范围
// 2. 起始时间和结束时间是方框表示
// 3. 之间的时间是选中表示
var dateBegin = this[SELECTED][0].join('-').toDate(),
dateEnd = this[SELECTED][1].join('-').toDate();
// 各个时间的时间戳
var timeNow = dateNow.getTime(), timeBegin = dateBegin.getTime(), timeEnd = dateEnd.getTime();
if (timeNow >= timeBegin && timeNow <= timeEnd) {
// 在时间范围之内
cl = cl + ' ' + SELECTED;
// 跟开始时间一样
if (timeNow == timeBegin) {
cl = cl + ' ' + prefixDate + 'begin';
}
// 跟结束时间一样
if (timeNow == timeEnd) {
cl = cl + ' ' + prefixDate + 'end';
}
// 今月的第一天还是最后一天
if (dayNow == 1) {
cl = cl + ' ' + prefixDate + 'first';
} else if (dayNow == monthDay[newDate.getMonth()]) {
cl = cl + ' ' + prefixDate + 'last';
}
}
// 如果在日期范围内
// 直接使用时间对象 Date 类作比较
if (dateNow >= min && dateNow <= max) {
htmlDate = htmlDate + '<a href="javascript:;" '+ htmlData +' class="'+ cl +'">'+ dayNow +'</a>';
} else {
htmlDate = htmlDate + '<span class="'+ cl +'">'+ dayNow +'</span>';
}
} else {
htmlDate = htmlDate + '<span class="'+ cl +'"> </span>';
}
}
}
}
htmlDate += '</div>';
}
return htmlDate;
}).call(this) + '</div>';
return {
monthDay: monthDay,
html: html,
min: min,
max: max
};
};
// 当前日期下这一年每月最大的日期数目
DateTime.prototype._monthDay = function(date) {
var arrDate = date;
if ($.isArray(date) == false) {
arrDate = date.toArray();
}
var monthDay = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// 如果是闰年
if (((arrDate[0] % 4 == 0) && (arrDate[0] % 100 != 0)) || (arrDate[0] % 400 == 0)) {
monthDay[1] = 29;
}
return monthDay;
},
// 上个月同日期
DateTime.prototype._datePrevMonth = function(date) {
// add on 2016-03-31
// 原先的加减月份的跨月计算是有些问题的
// 因为例如31号的下一月,系统会自动变成下下个月的1号
var arrDate = date;
if ($.isArray(date) == false) {
// 日期数组化
arrDate = date.toArray();
}
var month = arrDate[1]*1, monthDay = this._monthDay(arrDate);
if (month == 1) {
// 上个月是前一年的12月
// 12月有31号,无压力
return [arrDate[0]-1, 12, arrDate[2]].join('-').toDate();
}
// 其他月份不跨年
// 如果上一个月的最后1天小于本月的最后1天
// 嘿嘿嘿嘿
// 年不变,月减一,日期下个月
if (monthDay[month-2] < arrDate[2]) {
return [arrDate[0], month-1, monthDay[month-2]].join('-').toDate();
}
// 年不变,月减一,日期不变
return [arrDate[0], month-1, arrDate[2]].join('-').toDate();
},
// 下个月
DateTime.prototype._dateNextMonth = function(date) {
var arrDate = date;
if ($.isArray(date) == false) {
// 日期数组化
arrDate = date.toArray();
}
var month = arrDate[1]*1, monthDay = this._monthDay(arrDate);
if (month == 12) {
// 下个月跨年了
// 1月份也有31天,无压力
return [arrDate[0]+1, 1, arrDate[2]].join('-').toDate();
}
// 其他月份不跨年
// 如果下个月最后1天小于本月最后1天,例如,3月31日
if (monthDay[month] < arrDate[2]) {
return [arrDate[0], month+1, monthDay[month]].join('-').toDate();
}
// 其他时候正常
return [arrDate[0], month+1, arrDate[2]].join('-').toDate();
},
// 选择日期
DateTime.prototype.date = function() {
var arrDate = this[SELECTED];
// 目前日期对象
var currentDate = arrDate.join('-').toDate(),
// 上一个月
prevMonth = arrDate[1] - 1
// 下一个月
nextMonth = arrDate[1] * 1 + 1;
var obj = this._calendar(arrDate);
// 选择日期的完整HTML代码
// 1. 日期专属类名容器
var html = '<div class="'+ prefixDate +'x">';
// 2. 头部月份切换
html = html + '<div class="'+ prefixDate +'head">';
// 根据前后月份是否在范围之外,决定使用的标签类型
// span标签则是禁用态,本组件全部都是如此
// 2.1 上个月
// datePrevMonth指上个月日期
var datePrevMonth = this._datePrevMonth(arrDate),
// prevMonth指上个月
prevMonthGet = datePrevMonth.getMonth(),
prevYearGet = datePrevMonth.getFullYear();
//if (datePrevMonth >= obj.min && datePrevMonth <= obj.max) {
// add on 2015-12-01
// 原来的判断逻辑有问题
// 只要上个月份的最大日期比最小限制大就可以了
if (new Date(prevYearGet, prevMonthGet, obj.monthDay[prevMonthGet]) >= obj.min && datePrevMonth <= obj.max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-month="'+ prevMonth +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
}
// 2.2 下个月
var dateNextMonth = this._dateNextMonth(arrDate),
nextMonthGet = dateNextMonth.getMonth(),
nextYearGet = dateNextMonth.getFullYear();
//if (dateNextMonth >= obj.min && dateNextMonth <= obj.max) {
// add on 2015-12-01
// 经反馈,如果当前日期是30号,但是最大日期是下月1号,则下个月不能进入
// 这是有问题的
// 原因是此时的dateNextMonth > obj.max
// 因此判断应该从月初
if (dateNextMonth >= obj.min && new Date(nextYearGet, nextMonthGet, 1) <= obj.max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-month="'+ nextMonth +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
}
// 头部月份公用结束
html = html + '<a href="javascript:" class="'+ prefixDate +'switch" data-type="month">'+ arrDate.slice(0,2).join('-') +'</a>\
</div>';
// 3. 主体内容来自_calendar()方法
html += obj.html;
// 今天
// 首先,今天要在时间范围内
if (new Date() >= obj.min && new Date() <= obj.max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'item ' + prefixDate +'now">今天</a>';
} else {
html = html + '<span class="'+ prefixDate +'item ' + prefixDate +'now">今天</span>';
}
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'date').html(html);
return this;
};
// 选择日期范围
DateTime.prototype['date-range'] = function() {
var arrDates = this[SELECTED];
// 当前起始日期
// 默认(第一次)打开使用选中日期
// 如果有,使用面板存储月份
// 因为range选择不是即时更新文本框
var arrDate = this.el.container.data('date') || arrDates[0];
this.el.container.data('date', arrDate);
// 前一个月
var prevMonth = arrDate[1] - 1,
// 后一个月
nextMonth = arrDate[1] * 1 + 1;
// 根据起始时间创建新的时间对象
// var dateBegin = arrDate.join('-').toDate();
// 含时间范围和对应月份日历HTML的对象
var obj = this._calendar(arrDate);
// 选择时间范围完整HTML代码
// 1. 同样的,range容器
var html = '<div class="'+ prefixRange +'x">';
// 2. 头部
html = html + '<div class="'+ prefixDate +'head">\
<div class="'+ prefixDate +'half">';
// 2.1 上一个月箭头
var datePrevMonth = new Date(arrDate[0], prevMonth - 1, arrDate[2]);
if (datePrevMonth >= obj.min && datePrevMonth <= obj.max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-month="'+ prevMonth +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
}
// 今月月份显示
html = html + '<span class="'+ prefixDate +'switch">'+ new Date(arrDate[0], prevMonth, arrDate[2]).toArray().slice(0,2).join('-') +'</span>\
</div>\
<div class="'+ prefixDate +'half">';
// 2.2 下下个月
var dateNextMonth = new Date(arrDate[0], arrDate[1] , /*arrDate[2]*/1), dateAfterMonth = new Date(arrDate[0], nextMonth, arrDate[2]);
// 例如是3月31日
// 此时 arrDate[1] '03'
// nextMonth 是 4
// 此时原来的实现是由bug的
// 因为下个月没有4月31日,于是,就会变成5月
// 因此arrDate[2]应使用1代替
// 下个月的当前日期在合理范围之内,则使用该月
if (dateAfterMonth >= obj.min && dateAfterMonth <= obj.max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-month="'+ nextMonth +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
}
//
// 下月月份显示
html = html + '<span class="'+ prefixDate +'switch">'+ dateNextMonth.toArray().slice(0,2).join('-') +'</span>\
</div>';
// 头部闭合
html += '</div>';
// 3. 两个主体列表
// 这里要嵌套一层range特有的body
html = html + '<div class="'+ prefixRange +'body">' +
// 根据_calendar()方法创建两个月份日历
'<div class="'+ prefixDate +'half">' + obj.html + '</div>' +
'<div class="'+ prefixDate +'half">' + this._calendar(dateNextMonth.toArray()).html + '</div>' +
// 主体标签闭合
'</div>';
// 4. 确定与取消按钮
html = html + '<div class="'+ prefixRange +'footer">' +
'<a href="javascript:;" class="ui-button ui-button-primary" data-type="ensure">确定</a>' +
'<a href="javascript:;" class="ui-button" data-type="cancel">取消</a>' +
'</div>';
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'date-range').html(html);
return this;
};
// 月份组装
DateTime.prototype._month = function(arrDate) {
// 文本框元素比较常用,使用局部变量,一是节约文件大小,二是性能更好!
var input = this.el.input;
// 最大月份和最小月份
var min = input.attr('min') || this.min, max = input.attr('max') || this.max;
// 这里使用年月组合做比对
var arr = $.map([min, max], function(min_max, index) {
if (typeof min_max == 'object' && min_max.getTime) {
min_max = min_max.toArray().slice(0,2).join('');
} else if (typeof min_max == 'string' && /\D/.test(min_max.replace(regDate, '')) == false) {
// 这里对字符串进行比较简单的合法性判别
// 自己人使用不会乱来的
min_max = min_max.replace(regDate, '').slice(0, 6);
} else {
min_max = index? '999912': '000000';
}
return min_max;
});
min = arr[0];
max = arr[1];
var chinese = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
// 年份
var year = arrDate[0] * 1;
// 类型
var type = this.type;
var html = '<div class="'+ prefixMonth +'body">'+ (function() {
var htmlDate = '', cl = '', month = '';
for (var i=1; i<=12; i+=1) {
// 合法格式字符串
if (i < 10) {
month = '0' + i;
} else {
month = i + '';
}
// 基本类名
cl = prefixDate +'item';
if (type == 'month') {
if (i == arrDate[1]) {
// 选中态的类名
cl = cl + ' ' + SELECTED;
}
} else if (type == 'month-range') {
// range选择的匹配规则如下:
// 1. 获得已经选中到时间范围
// 2. 起始时间和结束时间是选中表示
// 3. 之间的时间也是选中表示
var strBegin = this[SELECTED][0].slice(0,2).join(''),
strEnd = this[SELECTED][1].slice(0,2).join('');
var strNow = year + month;
if (strNow >= strBegin && strNow <= strEnd) {
cl = cl + ' ' + SELECTED;
}
}
// 是否在范围以内
if (year + month >= min && year + month <= max) {
htmlDate = htmlDate + '<a href="javascript:" class="'+ cl +'" data-year="'+ year +'" data-value="'+ month +'">'+ chinese[i-1] +'月</a>';
} else {
htmlDate = htmlDate + '<span class="'+ cl +'" data-value="'+ month +'">'+ chinese[i-1] +'月</span>';
}
}
return htmlDate;
}).call(this) +
'</div>';
return {
html: html,
min: min,
max: max
};
};
// 选择月份
DateTime.prototype.month = function() {
var arrDate = this[SELECTED];
var obj = this._month(arrDate);
var min = obj.min, max = obj.max;
// 选择月份的完整HTML代码
// 1. month专属类名容器
var html = '<div class="'+ prefixMonth +'x">';
// 2. 月份切换的头部
var year = arrDate[0] * 1;
// 为什么呢?因为年份的范围是当前年份前面6个,后面5个
// 例如,假设今年是2015年,则头部年份范围是2009-2020
html = html + '<div class="'+ prefixDate +'head">';
// 2.1 是否还有上一年
if (year-1 >= Math.floor(min/100) && year-1 <= Math.floor(max/100)) {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-year="'+ (year-1) +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
}
// 2.2 是否还有下一年
if (year+1 >= Math.floor(min/100) && year+1 <= Math.floor(max/100)) {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-year="'+ (year+1) +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
}
// 头部结束
html = html + '<a href="javascript:" class="'+ prefixDate +'switch" data-type="year">'+ year +'</a>\
</div>';
// 3. 月份切换主体列表
html = html + obj.html;
// 今月
// 首先,今月要在时间范围内
var thisYearMonth = new Date().toArray().slice(0,2).join('');
if (thisYearMonth >= min && thisYearMonth <= max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'item ' + prefixDate +'now">今月</a>';
} else {
html = html + '<span class="'+ prefixDate +'item ' + prefixDate +'now">今月</span>';
}
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'month').html(html);
return this;
};
// 选择月份范围
DateTime.prototype['month-range'] = function() {
var arrDates = this[SELECTED];
// 当前起始日期
// 默认(第一次)打开使用选中日期
// 如果有,使用面板存储月份
// 因为range选择不是即时更新文本框
var arrDate = this.el.container.data('date') || arrDates[0];
this.el.container.data('date', arrDate);
// 前一年
var prevYear = arrDate[0]*1 - 1,
// 后一个年
nextYear = arrDate[0]*1 + 1;
// 含时间范围和对应月份日历HTML的对象
var obj = this._month(arrDate);
// 最大年份
var maxYear = obj.max.slice(0,4), minYear = obj.min.slice(0,4);
// 选择时间范围完整HTML代码
// 1. 同样的,range容器
var html = '<div class="'+ prefixRange +'x">';
// 2. 头部
html = html + '<div class="'+ prefixDate +'head">\
<div class="'+ prefixDate +'half">';
// 2.1 上一年箭头
if (prevYear >= minYear && prevYear <= maxYear) {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-year="'+ prevYear +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
}
// 今年年份显示
html = html + '<span class="'+ prefixDate +'switch">'+ arrDate[0] +'</span>\
</div>\
<div class="'+ prefixDate +'half">';
// 2.2 下一年
if (nextYear >= minYear && nextYear < maxYear) {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-year="'+ nextYear +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
}
// 下月月份显示
html = html + '<span class="'+ prefixDate +'switch">'+ nextYear +'</span>\
</div>';
// 头部闭合
html += '</div>';
// 3. 两个主体列表
// 这里要嵌套一层range特有的body
html = html + '<div class="'+ prefixRange +'body">' +
// 根据_calendar()方法创建两个月份日历
'<div class="'+ prefixDate +'half">' + obj.html + '</div>' +
'<div class="'+ prefixDate +'half">' + this._month([nextYear, arrDate[1], arrDate[2]]).html + '</div>' +
// 主体标签闭合
'</div>';
// 4. 确定与取消按钮
html = html + '<div class="'+ prefixRange +'footer">' +
'<a href="javascript:;" class="ui-button ui-button-primary" data-type="ensure">确定</a>' +
'<a href="javascript:;" class="ui-button" data-type="cancel">取消</a>' +
'</div>';
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'month-range').html(html);
return this;
}
// 选择年份
DateTime.prototype.year = function() {
var arrDate = this[SELECTED];
// 文本框元素比较常用,使用局部变量,一是节约文件大小,二是性能更好!
var input = this.el.input;
// 最小年份和最大年份
var min = input.attr('min') || this.min, max = input.attr('max') || this.max;
// 用户可能使用时间对象
if (typeof min == 'object' && min.getFullYear) {
min = min.getFullYear();
} else if (typeof min == 'string' && /\D/.test(min.replace(regDate, '')) == false) {
// 这里对字符串进行比较简单的合法性判别
// 自己人使用不会乱来的
min = min.toDate().getFullYear();
} else {
min = 0;
}
if (typeof max == 'object' && max.getFullYear) {
max = max.getFullYear();
} else if (typeof max == 'string' && /\D/.test(max.replace(regDate, '')) == false) {
max = max.toDate().getFullYear();
} else {
max = 9999;
}
// 选择年份的完整HTML代码
// 1. 同样的,year专属类名容器
var html = '<div class="'+ prefixYear +'x">';
// 2. 头部的年份切换,一切就是12年
// 有必要先知道当前的年份
var year = arrDate[0];
// 为什么呢?因为年份的范围是当前年份前面6个,后面5个
// 例如,假设今年是2015年,则头部年份范围是2009-2020
// 左右切换是没有限制的
html = html + '<div class="'+ prefixDate +'head">';
// 年份不是你想翻就能翻到
// 2.1 上一个12年
if (year-12 >= min && year-12 <= max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-year="'+ (year-12) +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
}
// 2.2 下一个12年
if (year+12 >= min && year+12 <= max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-year="'+ (year+12) +'">'+ this.svg +'</a>';
} else {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
}
// year选择是顶级类别,一定是不可点击的
html = html + '<span class="'+ prefixDate +'switch">'+ [year-6, year+5].join('-') +'</span></div>';
// 3. 年份选择的主体
html = html + '<div class="'+ prefixYear +'body">'+ (function() {
var htmlDate = '', cl = '';
for (var i=year-6; i<year+6; i+=1) {
// 选中态的类名
cl = prefixDate +'item';
if (i == year) {
cl = cl + ' ' + SELECTED;
}
// 是否在范围以内
if (i >= min && i <= max) {
htmlDate = htmlDate + '<a href="javascript:" class="'+ cl +'">'+ i +'</a>';
} else {
htmlDate = htmlDate + '<span class="'+ cl +'">'+ i +'</span>';
}
}
return htmlDate;
})() +
'</div>';
// 今年
// 首先,今年要在时间范围内
var thisYear = new Date().getFullYear();
if (thisYear >= min && thisYear <= max) {
html = html + '<a href="javascript:" class="'+ prefixDate +'item ' + prefixDate +'now">今年</a>';
} else {
html = html + '<span class="'+ prefixDate +'item ' + prefixDate +'now">今年</span>';
}
// 头部标签闭合
html += '</div>';
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'year').html(html);
return this;
};
// 选择小时
DateTime.prototype.hour = function() {
var arrTime = this[SELECTED];
// 文本框元素比较常用,使用局部变量,一是节约文件大小,二是性能更好!
var input = this.el.input;
// 时间选择的小时间隔,默认是1小时
var step = input.attr('step') * 1;
if (this.type != 'hour' || !step || step < 1) {
step = 1;
} else {
step = Math.round(step);
}
// 最小时间,和最大时间
// 这里只比较小时
var min = (input.attr('min') || this.min.toString()).split(':')[0], max = (input.attr('max') || this.max.toString()).split(':')[0];
if (/\D/.test(min)) {
min = 0;
} else {
min = min * 1;
}
if (/\D/.test(max)) {
max = 24;
} else {
max = max * 1;
}
// 选择小时的完整HTML
// 1. 同样的,专有容器
var html = '<div class="'+ prefixHour +'x">';
// 2. 小时没有头部切换,直接列表们
html = html + '<div class="'+ prefixHour +'body">'+ (function() {
var htmlTime = '', hour = '', cl = '';
for (var i=0; i<24; i+=step) {
hour = i + '';
if (hour.length == 1) {
hour = '0' + hour;
}
// 选中态的类名
cl = prefixDate +'item';
if (hour == arrTime[0]) {
cl = cl + ' ' + SELECTED;
}
// 是否在范围以内
if (hour >= min && hour <= max) {
htmlTime = htmlTime + '<a href="javascript:" class="'+ cl +'">'+ hour +':00</a>';
} else {
htmlTime = htmlTime + '<span class="'+ cl +'">'+ hour +':00</span>';
}
}
return htmlTime;
})() +
'</div>';
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'hour').html(html);
return this;
};
// 选择分钟
DateTime.prototype.minute = function() {
var arrTime = this[SELECTED];
// 文本框元素比较常用,使用局部变量,一是节约文件大小,二是性能更好!
var input = this.el.input;
// 分钟时间间隔, 默认为5分钟
var step = input.attr('step') * 1 || 5;
// 最小时间,和最大时间
// 跟小时不同,这里必须符合00:00格式
// 由于格式固定,我们直接去除':'后的值进行比较
// 例如:10:20 → 1020
var min = input.attr('min') || (this.min + ''), max = input.attr('max') || (this.max + '');
if (min == 'auto' || /\D/.test(min.replace(':', '')) || min.split(':').length != 2) {
min = 0;
} else {
min = min.replace(':', '') * 1;
}
if (max == 'auto' || /\D/.test(max.replace(':', '')) || max.split(':').length != 2) {
max = 2359;
} else {
max = max.replace(':', '') * 1;
}
// 选择分钟的完整HTML
// 1. 外部的容器,含有专有容器类名,可以重置内部的一些样式
var html = '<div class="'+ prefixMinute +'x">';
// 2. 头部小时的左右切换
var hour = arrTime[0] * 1;
// 首先是公共部分
html = html + '<div class="'+ prefixDate +'head">';
// 2.1 可不可往前翻
if (hour <= Math.floor(min / 100)) {
html = html + '<span class="'+ prefixDate +'prev">'+ this.svg +'</span>';
} else {
html = html + '<a href="javascript:" class="'+ prefixDate +'prev" data-hour="'+ (hour-1) +'">'+ this.svg +'</a>';
}
// 2.2 可不可以往后翻
if (hour >= Math.floor(max / 100)) {
html = html + '<span class="'+ prefixDate +'next">'+ this.svg +'</span>';
} else {
html = html + '<a href="javascript:" class="'+ prefixDate +'next" data-hour="'+ (hour+1) +'">'+ this.svg +'</a>';
}
// 头部结束的公共html部分
html = html + '<a href="javascript:" class="'+ prefixDate +'switch" data-type="hour">'+ arrTime[0] +':00</a></div>';
// 3. 分钟主体
html = html + '<div class="'+ prefixMinute +'body">'+ (function() {
var htmlTime = '', minute = '', cl = '';
for (var i=0; i<60; i+=step) {
minute = i + '';
if (minute.length == 1) {
minute = '0' + minute;
}
// 基本样式
cl = prefixDate +'item';
// 是否在时间范围内
if ((arrTime[0] + minute) * 1 >= min && (arrTime[0] + minute) * 1<= max) {
// 选中态
if (minute == arrTime[1]) {
cl = cl + ' ' + SELECTED;
}
htmlTime = htmlTime + '<a href="javascript:" class="'+ cl +'">'+ [arrTime[0], minute].join(':') +'</a>';
} else {
htmlTime = htmlTime + '<span class="'+ cl +'">'+ [arrTime[0], minute].join(':') +'</span>';
}
}
return htmlTime;
})() +
'</div>';
// 容器闭合标签
html += '</div>';
this.el.container.attr('data-type', 'minute').html(html);
return this;
};
DateTime.prototype.show = function() {
var container = this.el.container;
// 根据value更新SELECTED
this.format();
// 不同的类名显示不同的内容
if (this.type == 'time') {
this.minute();
} else if (this.type == 'date-range') {
// 存储当前日期范围数据,以便取消的时候可以正确还原
if (!this._rangeSelected) {
this._rangeSelected = this[SELECTED];
}
this['date-range']();
} else if (this.type == 'month-range') {
// 存储当前日期范围数据,以便取消的时候可以正确还原
if (!this._rangeSelected) {
this._rangeSelected = this[SELECTED];
}
this['month-range']();
} else if (this[this.type]) {
this[this.type]();
} else {
this.date();
}
if ($.contains($(document.body), container) == false) {
$(document.body).append(container);
}
container.show().follow(this.el.trigger.addClass(ACTIVE), {
position: "4-1"
});
if ($.isFunction(this.callback.show)) {
this.callback.show.call(this, this.el.input, container);
}
this.display = true;
return this;
};
DateTime.prototype.hide = function() {
this.el.container.hide();
this.el.trigger.removeClass(ACTIVE);
if ($.isFunction(this.callback.hide)) {
this.callback.hide.call(this, this.el.input, this.el.container);
}
this.display = false;
return this;
};
// 下面为拓展的jQuery包装器方法
$.fn.dateTime = function(options) {
return $(this).each(function() {
if (!$(this).data('dateTime')) {
$(this).data('dateTime', new DateTime($(this), options));
}
});
};
return DateTime;
}));