-
-
buildDate
-
buildDate UTC
+
+
buildDate
+
buildDate UTC
-
{{ entry.source }}
-
-
+
{{entry.source}}
+
+
-
-
adjustDate
-
source, change, build, result
+
adjustDate
+
change
+
build
+
result
-
{{ entry.source }}
-
-
-
+
{{entry.source}}
+
+
+
@@ -77,10 +78,10 @@ const formatUTC = d => d.toISOString().replace(/Z$/, '+00:00')
export default {
created () {
const buildSeed = [
- { year: 2021, date: 31, month: 7 },
- { year: 2021, month: 7, date: 31 },
- { month: 2, date: 29, year: 2020 },
- { month: 2, date: 29, year: 2021 }
+ { year: 2021, date: 31, month: 7, hours: 10 },
+ { hours: 10, year: 2021, month: 7, date: 31 },
+ { month: 2, date: 29, year: 2020, hours: 10 },
+ { hours: 10, month: 2, date: 29, year: 2021 }
]
this.buildDateSeed = buildSeed.map(entry => {
return {
@@ -106,11 +107,13 @@ export default {
}
})
},
+
data () {
return {
date: format(new Date())
}
},
+
computed: {
startOfDate () {
if (isNaN((new Date(this.date)).valueOf()) === true) {
diff --git a/ui/src/components/date/QDate.js b/ui/src/components/date/QDate.js
index 3e85d3ca8501..5543ebff7633 100644
--- a/ui/src/components/date/QDate.js
+++ b/ui/src/components/date/QDate.js
@@ -5,7 +5,7 @@ import QBtn from '../btn/QBtn.js'
import DateTimeMixin from '../../mixins/datetime.js'
import { slot } from '../../utils/private/slot.js'
-import { formatDate, __splitDate, getDateDiff } from '../../utils/date.js'
+import { formatDate, __splitDate, getDateDiff, __safeCreateDate } from '../../utils/date.js'
import { pad } from '../../utils/format.js'
import { jalaaliMonthLength, toGregorian } from '../../utils/private/date-persian.js'
import cache from '../../utils/private/cache.js'
@@ -15,8 +15,25 @@ const yearsInterval = 20
const views = [ 'Calendar', 'Years', 'Months' ]
const viewIsValid = v => views.includes(v)
const yearMonthValidator = v => /^-?[\d]+\/[0-1]\d$/.test(v)
+const modelNavigationValidator = v => [ 'from', 'to', false ].indexOf(v) > -1
+const hashToInt = s => {
+ if (typeof s !== 'string') {
+ return NaN
+ }
+
+ const splits = s.split('/')
+
+ return parseInt(splits[0], 10) * 10000 + parseInt(splits.slice(1).join(''), 10)
+}
const lineStr = ' \u2014 '
+function modelIsValid (date) {
+ return date.dateHash !== null &&
+ date.day !== null &&
+ date.month !== null &&
+ date.year !== null
+}
+
export default Vue.extend({
name: 'QDate',
@@ -30,6 +47,8 @@ export default Vue.extend({
multiple: Boolean,
range: Boolean,
+ dayAsRange: Boolean,
+
title: String,
subtitle: String,
@@ -53,6 +72,12 @@ export default Vue.extend({
options: [ Array, Function ],
+ modelNavigation: {
+ type: [ String, Boolean ],
+ default: 'from',
+ validator: modelNavigationValidator
+ },
+
navigationMinYearMonth: {
type: String,
validator: yearMonthValidator
@@ -160,28 +185,36 @@ export default Vue.extend({
return this.normalizedModel
.filter(date => typeof date === 'string')
.map(date => this.__decodeString(date, this.innerMask, this.innerLocale))
- .filter(date =>
- date.dateHash !== null &&
- date.day !== null &&
- date.month !== null &&
- date.year !== null
- )
+ .filter(modelIsValid)
},
rangeModel () {
const fn = date => this.__decodeString(date, this.innerMask, this.innerLocale)
return this.normalizedModel
.filter(date => isObject(date) === true && date.from !== void 0 && date.to !== void 0)
- .map(range => ({ from: fn(range.from), to: fn(range.to) }))
- .filter(range => range.from.dateHash !== null && range.to.dateHash !== null && range.from.dateHash < range.to.dateHash)
+ .map(range => {
+ const from = fn(range.from)
+ const to = fn(range.to)
+
+ return hashToInt(from.dateHash) <= hashToInt(to.dateHash)
+ ? { from, to }
+ : { from: to, to: from }
+ })
+ .filter(range => modelIsValid(range.from) === true && modelIsValid(range.to) === true)
},
getNativeDateFn () {
return this.calendar !== 'persian'
- ? model => new Date(model.year, model.month - 1, model.day)
+ ? model => {
+ const date = __safeCreateDate(model.year, model.month - 1, model.day)
+ date.setFullYear(model.year)
+ return date
+ }
: model => {
const gDate = toGregorian(model.year, model.month, model.day)
- return new Date(gDate.gy, gDate.gm - 1, gDate.gd)
+ const date = __safeCreateDate(gDate.gy, gDate.gm - 1, gDate.gd)
+ date.setFullYear(model.year)
+ return date
}
},
@@ -189,7 +222,7 @@ export default Vue.extend({
return this.calendar === 'persian'
? this.__getDayHash
: (date, mask, locale) => formatDate(
- new Date(
+ __safeCreateDate(
date.year,
date.month - 1,
date.day,
@@ -237,7 +270,7 @@ export default Vue.extend({
return `${this.daysInModel} ${this.innerLocale.pluralDay}`
}
- const model = this.daysModel[0]
+ const model = this.minSelectedModel
const date = this.getNativeDateFn(model)
if (isNaN(date.valueOf()) === true) {
@@ -278,7 +311,7 @@ export default Vue.extend({
) + ' ' + to.year
}
- return this.daysModel[0].year
+ return this.minSelectedModel.year
},
minSelectedModel () {
@@ -317,10 +350,21 @@ export default Vue.extend({
},
daysInMonth () {
- const date = this.viewModel
- return this.calendar !== 'persian'
- ? (new Date(date.year, date.month, 0)).getDate()
- : jalaaliMonthLength(date.year, date.month)
+ const { year, month } = this.viewModel
+
+ if (this.calendar !== 'persian') {
+ return {
+ prev: (__safeCreateDate(year, month - 1, 0)).getDate(),
+ cur: (__safeCreateDate(year, month, 0)).getDate(),
+ next: (__safeCreateDate(year, month + 1, 0)).getDate()
+ }
+ }
+
+ return {
+ prev: jalaaliMonthLength(month === 1 ? year - 1 : year, month === 1 ? 12 : month - 1),
+ cur: jalaaliMonthLength(year, month),
+ next: jalaaliMonthLength(month === 12 ? year + 1 : year, month === 12 ? 1 : month + 1)
+ }
},
today () {
@@ -390,34 +434,35 @@ export default Vue.extend({
const map = {}
this.rangeModel.forEach(entry => {
- const hashFrom = this.__getMonthHash(entry.from)
- const hashTo = this.__getMonthHash(entry.to)
+ const fromHash = this.__getMonthHash(entry.from)
+ const toHash = this.__getMonthHash(entry.to)
+ const toHashInt = hashToInt(toHash)
- if (map[hashFrom] === void 0) {
- map[hashFrom] = []
+ if (map[fromHash] === void 0) {
+ map[fromHash] = []
}
- map[hashFrom].push({
+ map[fromHash].push({
from: entry.from.day,
- to: hashFrom === hashTo ? entry.to.day : void 0,
+ to: fromHash === toHash ? entry.to.day : void 0,
range: entry
})
- if (hashFrom < hashTo) {
- let hash
+ if (hashToInt(fromHash) < toHashInt) {
const { year, month } = entry.from
const cur = month < 12
? { year, month: month + 1 }
: { year: year + 1, month: 1 }
+ let hash = this.__getMonthHash(cur)
- while ((hash = this.__getMonthHash(cur)) <= hashTo) {
+ while (hashToInt(hash) <= toHashInt) {
if (map[hash] === void 0) {
map[hash] = []
}
map[hash].push({
from: void 0,
- to: hash === hashTo ? entry.to.day : void 0,
+ to: hash === toHash ? entry.to.day : void 0,
range: entry
})
@@ -426,6 +471,8 @@ export default Vue.extend({
cur.year++
cur.month = 1
}
+
+ hash = this.__getMonthHash(cur)
}
}
})
@@ -433,54 +480,64 @@ export default Vue.extend({
return map
},
- rangeView () {
+ rangeViewMap () {
if (this.editRange === void 0) {
- return
+ return {}
}
+ const map = {}
const { init, initHash, final, finalHash } = this.editRange
- const [ from, to ] = initHash <= finalHash
+ const [ from, to ] = hashToInt(initHash) <= hashToInt(finalHash)
? [ init, final ]
: [ final, init ]
- const fromHash = this.__getMonthHash(from)
- const toHash = this.__getMonthHash(to)
+ const fromHashInt = hashToInt(this.__getMonthHash(from))
+ const toHashInt = hashToInt(this.__getMonthHash(to))
- if (fromHash !== this.viewMonthHash && toHash !== this.viewMonthHash) {
- return
- }
+ const months = [ 'prev', 'cur', 'next' ]
- const view = {}
+ months.forEach(month => {
+ const monthHashInt = hashToInt(this.viewMonthHash[month])
- if (fromHash === this.viewMonthHash) {
- view.from = from.day
- view.includeFrom = true
- }
- else {
- view.from = 1
- }
+ if (fromHashInt > monthHashInt || toHashInt < monthHashInt) {
+ return
+ }
- if (toHash === this.viewMonthHash) {
- view.to = to.day
- view.includeTo = true
- }
- else {
- view.to = this.daysInMonth
- }
+ const view = {
+ includeFrom: fromHashInt === monthHashInt,
+ includeTo: toHashInt === monthHashInt
+ }
+
+ view.from = view.includeFrom ? from.day : 1
+ view.to = view.includeTo ? to.day : this.daysInMonth[month]
- return view
+ map[this.viewMonthHash[month]] = view
+ })
+
+ return map
},
viewMonthHash () {
- return this.__getMonthHash(this.viewModel)
+ const { year, month } = this.viewModel
+ return {
+ prev: this.__getMonthHash({
+ year: month === 1 ? year - 1 : year,
+ month: month === 1 ? 12 : month - 1
+ }),
+ cur: this.__getMonthHash({ year, month }),
+ next: this.__getMonthHash({
+ year: month === 12 ? year + 1 : year,
+ month: month === 12 ? 1 : month + 1
+ })
+ }
},
selectionDaysMap () {
const map = {}
if (this.options === void 0) {
- for (let i = 1; i <= this.daysInMonth; i++) {
+ for (let i = 1; i <= this.daysInMonth.cur; i++) {
map[i] = true
}
@@ -491,8 +548,8 @@ export default Vue.extend({
? this.options
: date => this.options.includes(date)
- for (let i = 1; i <= this.daysInMonth; i++) {
- const dayHash = this.viewMonthHash + '/' + pad(i)
+ for (let i = 1; i <= this.daysInMonth.cur; i++) {
+ const dayHash = this.viewMonthHash.cur + '/' + pad(i)
map[i] = fn(dayHash)
}
@@ -503,7 +560,7 @@ export default Vue.extend({
const map = {}
if (this.events === void 0) {
- for (let i = 1; i <= this.daysInMonth; i++) {
+ for (let i = 1; i <= this.daysInMonth.cur; i++) {
map[i] = false
}
}
@@ -512,8 +569,8 @@ export default Vue.extend({
? this.events
: date => this.events.includes(date)
- for (let i = 1; i <= this.daysInMonth; i++) {
- const dayHash = this.viewMonthHash + '/' + pad(i)
+ for (let i = 1; i <= this.daysInMonth.cur; i++) {
+ const dayHash = this.viewMonthHash.cur + '/' + pad(i)
map[i] = fn(dayHash) === true && this.evtColor(dayHash)
}
}
@@ -521,47 +578,42 @@ export default Vue.extend({
return map
},
- viewDays () {
- let date, endDay
+ startFillDays () {
+ let date
const { year, month } = this.viewModel
if (this.calendar !== 'persian') {
- date = new Date(year, month - 1, 1)
- endDay = (new Date(year, month - 1, 0)).getDate()
+ date = __safeCreateDate(year, month - 1, 1)
}
else {
const gDate = toGregorian(year, month, 1)
- date = new Date(gDate.gy, gDate.gm - 1, gDate.gd)
- let prevJM = month - 1
- let prevJY = year
- if (prevJM === 0) {
- prevJM = 12
- prevJY--
- }
- endDay = jalaaliMonthLength(prevJY, prevJM)
+ date = __safeCreateDate(gDate.gy, gDate.gm - 1, gDate.gd)
}
+ const days = date.getDay() - this.computedFirstDayOfWeek - 1
+
return {
- days: date.getDay() - this.computedFirstDayOfWeek - 1,
- endDay
+ days: days < 0 ? days + 7 : days,
+ endDay: this.daysInMonth.prev
}
},
days () {
const res = []
- const { days, endDay } = this.viewDays
+ const { days, endDay } = this.startFillDays
- const len = days < 0 ? days + 7 : days
- if (len < 6) {
- for (let i = endDay - len; i <= endDay; i++) {
- res.push({ i, fill: true })
+ if (days < 6) {
+ for (let i = endDay - days; i <= endDay; i++) {
+ res.push({ i, day: this.viewMonthHash.prev + '/' + pad(i), fill: true })
}
+
+ this.__fillDaysMeta(res, this.viewMonthHash.prev, endDay, -endDay + days + 1, endDay - days, endDay)
}
const index = res.length
- for (let i = 1; i <= this.daysInMonth; i++) {
- const day = { i, event: this.eventDaysMap[i], classes: [] }
+ for (let i = 1; i <= this.daysInMonth.cur; i++) {
+ const day = { i, day: this.viewMonthHash.cur + '/' + pad(i), event: this.eventDaysMap[i], classes: [] }
if (this.selectionDaysMap[i] === true) {
day.in = true
@@ -571,93 +623,7 @@ export default Vue.extend({
res.push(day)
}
- // if current view has days in model
- if (this.daysMap[this.viewMonthHash] !== void 0) {
- this.daysMap[this.viewMonthHash].forEach(day => {
- const i = index + day - 1
- Object.assign(res[i], {
- selected: true,
- unelevated: true,
- flat: false,
- color: this.computedColor,
- textColor: this.computedTextColor
- })
- })
- }
-
- // if current view has ranges in model
- if (this.rangeMap[this.viewMonthHash] !== void 0) {
- this.rangeMap[this.viewMonthHash].forEach(entry => {
- if (entry.from !== void 0) {
- const from = index + entry.from - 1
- const to = index + (entry.to || this.daysInMonth) - 1
-
- for (let day = from; day <= to; day++) {
- Object.assign(res[day], {
- range: entry.range,
- unelevated: true,
- color: this.computedColor,
- textColor: this.computedTextColor
- })
- }
-
- Object.assign(res[from], {
- rangeFrom: true,
- flat: false
- })
-
- entry.to !== void 0 && Object.assign(res[to], {
- rangeTo: true,
- flat: false
- })
- }
- else if (entry.to !== void 0) {
- const to = index + entry.to - 1
-
- for (let day = index; day <= to; day++) {
- Object.assign(res[day], {
- range: entry.range,
- unelevated: true,
- color: this.computedColor,
- textColor: this.computedTextColor
- })
- }
-
- Object.assign(res[to], {
- flat: false,
- rangeTo: true
- })
- }
- else {
- const to = index + this.daysInMonth - 1
- for (let day = index; day <= to; day++) {
- Object.assign(res[day], {
- range: entry.range,
- unelevated: true,
- color: this.computedColor,
- textColor: this.computedTextColor
- })
- }
- }
- })
- }
-
- if (this.rangeView !== void 0) {
- const from = index + this.rangeView.from - 1
- const to = index + this.rangeView.to - 1
-
- for (let day = from; day <= to; day++) {
- res[day].color = this.computedColor
- res[day].editRange = true
- }
-
- if (this.rangeView.includeFrom === true) {
- res[from].editRangeFrom = true
- }
- if (this.rangeView.includeTo === true) {
- res[to].editRangeTo = true
- }
- }
+ this.__fillDaysMeta(res, this.viewMonthHash.cur, this.daysInMonth.cur, index, 1, this.daysInMonth.cur)
if (this.viewModel.year === this.today.year && this.viewModel.month === this.today.month) {
res[index + this.today.day - 1].today = true
@@ -667,30 +633,29 @@ export default Vue.extend({
if (left > 0) {
const afterDays = 7 - left
for (let i = 1; i <= afterDays; i++) {
- res.push({ i, fill: true })
+ res.push({ i, day: this.viewMonthHash.next + '/' + pad(i), fill: true })
}
+
+ this.__fillDaysMeta(res, this.viewMonthHash.next, this.daysInMonth.next, index + this.daysInMonth.cur, 1, afterDays)
}
res.forEach(day => {
let cls = 'q-date__calendar-item '
- if (day.fill === true) {
- cls += 'q-date__calendar-item--fill'
- }
- else {
- cls += `q-date__calendar-item--${day.in === true ? 'in' : 'out'}`
+ cls += day.fill === true
+ ? 'q-date__calendar-item--fill'
+ : `q-date__calendar-item--${day.in === true ? 'in' : 'out'}`
- if (day.range !== void 0) {
- cls += ` q-date__range${day.rangeTo === true ? '-to' : (day.rangeFrom === true ? '-from' : '')}`
- }
+ if (day.range !== void 0 || day.editRange === true) {
+ cls += ` text-${day.color}`
+ }
- if (day.editRange === true) {
- cls += ` q-date__edit-range${day.editRangeFrom === true ? '-from' : ''}${day.editRangeTo === true ? '-to' : ''}`
- }
+ if (day.range !== void 0 && (day.rangeTo !== true || day.rangeFrom !== true)) {
+ cls += ` q-date__range${day.rangeFrom === true ? '-from' : (day.rangeTo === true ? '-to' : '')}`
+ }
- if (day.range !== void 0 || day.editRange === true) {
- cls += ` text-${day.color}`
- }
+ if (day.editRange === true) {
+ cls += ` q-date__edit-range${day.editRangeFrom === true ? '-from' : ''}${day.editRangeTo === true ? '-to' : ''}`
}
day.classes = cls
@@ -711,13 +676,7 @@ export default Vue.extend({
methods: {
setToday () {
- const date = this.today
- const month = this.daysMap[this.__getMonthHash(date)]
-
- if (month === void 0 || month.includes(date.day) === false) {
- this.__addToModel(date)
- }
-
+ this.__toggleDate(this.today, this.__getMonthHash(this.today))
this.setCalendarTo(this.today.year, this.today.month)
},
@@ -740,7 +699,11 @@ export default Vue.extend({
this.__updateViewModel(year, month)
},
- setEditingRange (from, to) {
+ setEditingRange (from, to, modelNavigation) {
+ if (modelNavigation === void 0) {
+ modelNavigation = this.modelNavigation
+ }
+
if (this.range === false || !from) {
this.editRange = void 0
return
@@ -758,7 +721,14 @@ export default Vue.extend({
finalHash: this.__getDayHash(final)
}
- this.setCalendarTo(init.year, init.month)
+ if ([ 'from', 'to' ].indexOf(modelNavigation) > -1) {
+ this.$nextTick(() => {
+ this.setCalendarTo(
+ modelNavigation === 'from' ? init.year : final.year,
+ modelNavigation === 'from' ? init.month : final.month
+ )
+ })
+ }
},
__getMask () {
@@ -781,6 +751,12 @@ export default Vue.extend({
},
__getViewModel (mask, locale) {
+ if (this.modelNavigation === false) {
+ return this.viewModel === void 0
+ ? this.__getDefaultViewModel()
+ : this.viewModel
+ }
+
const model = Array.isArray(this.value) === true
? this.value
: (this.value ? [ this.value ] : [])
@@ -789,8 +765,10 @@ export default Vue.extend({
return this.__getDefaultViewModel()
}
+ const target = model[this.modelNavigation === 'from' ? 0 : model.length - 1]
+
const decoded = this.__decodeString(
- model[0].from !== void 0 ? model[0].from : model[0],
+ target.from !== void 0 ? target.from : target,
mask,
locale
)
@@ -960,9 +938,19 @@ export default Vue.extend({
},
__getCalendarView (h) {
+ const dayContentFn = this.$scopedSlots.day !== void 0
+ ? this.$scopedSlots.day
+ : day => (day.event !== false ? [
+ h('div', { staticClass: 'q-date__event bg-' + day.event })
+ ] : null)
+ const dayFillContentFn = this.$scopedSlots.day !== void 0
+ ? this.$scopedSlots.day
+ : day => h('div', [ day.i ])
+
const selectedDay = this.days.find(day => day.unelevated === true)
const viewDay = selectedDay === void 0 ? this.days.find(day => day.today === true) : selectedDay
const viewTarget = viewDay === void 0 ? 1 : viewDay.i
+ const calCachePrefix = this.calendar === 'persian' ? 'dayP#' : 'day#'
return [
h('div', {
@@ -1006,7 +994,7 @@ export default Vue.extend({
}
}, [
h('div', {
- key: this.viewMonthHash,
+ key: this.viewMonthHash.cur,
staticClass: 'q-date__calendar-days fit'
}, this.days.map(day => h('div', { staticClass: day.classes }, [
day.in === true
@@ -1022,15 +1010,13 @@ export default Vue.extend({
label: day.i,
tabindex: this.computedTabindex
},
- on: cache(this, 'day#' + day.i, {
+ on: cache(this, calCachePrefix + day.i, {
click: () => { this.__onDayClick(day.i) },
focusin: () => { this.__onDayMouseover(day.i) },
mouseenter: () => { this.__onDayMouseover(day.i) }
})
- }, day.event !== false ? [
- h('div', { staticClass: 'q-date__event bg-' + day.event })
- ] : null)
- : h('div', [ day.i ])
+ }, dayContentFn(day))
+ : dayFillContentFn(day)
])))
])
])
@@ -1046,25 +1032,30 @@ export default Vue.extend({
(this.maxNav !== void 0 && this.viewModel.year === this.maxNav.year && this.maxNav.month < month)
)
}
+ const activeProps = {
+ unelevated: true,
+ color: this.computedColor,
+ textColor: this.computedTextColor
+ }
+ const inactiveProps = { flat: true }
const content = this.innerLocale.monthsShort.map((month, i) => {
- const active = this.viewModel.month === i + 1
-
return h('div', {
staticClass: 'q-date__months-item flex flex-center'
}, [
h(QBtn, {
staticClass: currentYear === true && this.today.month === i + 1 ? 'q-date__today' : null,
ref: this.viewModel.month === i + 1 ? 'viewTarget' : void 0,
- props: {
- flat: active !== true,
- label: month,
- unelevated: active,
- color: active === true ? this.computedColor : null,
- textColor: active === true ? this.computedTextColor : null,
- tabindex: this.computedTabindex,
- disable: isDisabled(i + 1)
- },
+ props: Object.assign(
+ {
+ label: month,
+ tabindex: this.computedTabindex,
+ disable: isDisabled(i + 1)
+ },
+ this.viewModel.month === i + 1
+ ? activeProps
+ : inactiveProps
+ ),
on: cache(this, 'month#' + i, { click: () => { this.__setMonth(i + 1) } })
})
])
@@ -1186,6 +1177,81 @@ export default Vue.extend({
])
},
+ __fillDaysMeta (res, monthHash, daysInMonth, index, dayFrom, dayTo) {
+ // if current view has days in model
+ if (this.daysMap[monthHash] !== void 0) {
+ this.daysMap[monthHash]
+ .filter(day => day >= dayFrom && day <= dayTo)
+ .forEach(day => {
+ const i = index + day - 1
+ Object.assign(res[i], {
+ selected: true,
+ unelevated: true,
+ flat: false,
+ color: this.computedColor,
+ textColor: this.computedTextColor
+ })
+ })
+ }
+
+ // if current view has ranges in model
+ if (this.rangeMap[monthHash] !== void 0) {
+ this.rangeMap[monthHash].forEach(entry => {
+ const from = index + Math.max(dayFrom, entry.from === void 0 ? 1 : entry.from) - 1
+ const to = index + Math.min(dayTo, entry.to === void 0 ? daysInMonth : entry.to) - 1
+
+ for (let day = from; day <= to; day++) {
+ Object.assign(res[day], {
+ range: entry.range,
+ unelevated: true,
+ color: this.computedColor,
+ textColor: this.computedTextColor
+ })
+ }
+
+ if (entry.from >= dayFrom && entry.from <= dayTo) {
+ Object.assign(res[from], {
+ rangeFrom: true,
+ flat: false
+ })
+ }
+
+ if (entry.to >= dayFrom && entry.to <= dayTo) {
+ Object.assign(res[to], {
+ rangeTo: true,
+ flat: false
+ })
+ }
+ })
+ }
+
+ if (this.rangeViewMap[monthHash] !== void 0) {
+ const from = index + Math.max(dayFrom, this.rangeViewMap[monthHash].from) - 1
+ const to = index + Math.min(dayTo, this.rangeViewMap[monthHash].to) - 1
+
+ for (let day = from; day <= to; day++) {
+ res[day].color = this.computedColor
+ res[day].editRange = true
+ }
+
+ if (
+ this.rangeViewMap[monthHash].includeFrom === true &&
+ this.rangeViewMap[monthHash].from >= dayFrom &&
+ this.rangeViewMap[monthHash].from <= dayTo
+ ) {
+ res[from].editRangeFrom = true
+ }
+
+ if (
+ this.rangeViewMap[monthHash].includeTo === true &&
+ this.rangeViewMap[monthHash].to >= dayFrom &&
+ this.rangeViewMap[monthHash].to <= dayTo
+ ) {
+ res[to].editRangeTo = true
+ }
+ }
+ },
+
__goToMonth (offset) {
let year = this.viewModel.year
let month = Number(this.viewModel.month) + offset
@@ -1242,14 +1308,14 @@ export default Vue.extend({
const day = { ...this.viewModel, day: dayIndex }
if (this.range === false) {
- this.__toggleDate(day, this.viewMonthHash)
+ this.__toggleDate(day, this.viewMonthHash.cur)
return
}
if (this.editRange === void 0) {
const dayProps = this.days.find(day => day.fill !== true && day.i === dayIndex)
- if (dayProps.range !== void 0) {
+ if (this.noUnset !== true && dayProps.range !== void 0) {
this.__removeFromModel({ target: day, from: dayProps.range.from, to: dayProps.range.to })
return
}
@@ -1274,12 +1340,12 @@ export default Vue.extend({
const
initHash = this.editRange.initHash,
finalHash = this.__getDayHash(day),
- payload = initHash <= finalHash
+ payload = hashToInt(initHash) <= hashToInt(finalHash)
? { from: this.editRange.init, to: day }
: { from: day, to: this.editRange.init }
this.editRange = void 0
- this.__addToModel(initHash === finalHash ? day : { target: day, ...payload })
+ this.__addToModel(initHash === finalHash && this.dayAsRange !== true ? day : { target: day, ...payload })
this.$emit('range-end', {
from: this.__getShortDate(payload.from),
@@ -1296,10 +1362,18 @@ export default Vue.extend({
final,
finalHash: this.__getDayHash(final)
})
+
+ this.$emit('range-change', {
+ from: this.__getShortDate(this.editRange.init),
+ to: this.__getShortDate(this.editRange.final)
+ })
}
},
__updateViewModel (year, month) {
+ year = parseInt(year, 10)
+ month = parseInt(month, 10)
+
if (this.minNav !== void 0 && year <= this.minNav.year) {
year = this.minNav.year
if (month < this.minNav.month) {
@@ -1317,7 +1391,7 @@ export default Vue.extend({
const newHash = year + '/' + pad(month) + '/01'
if (newHash !== this.viewModel.dateHash) {
- this.monthDirection = (this.viewModel.dateHash < newHash) === (this.$q.lang.rtl !== true) ? 'left' : 'right'
+ this.monthDirection = (hashToInt(this.viewModel.dateHash) < hashToInt(newHash)) === (this.$q.lang.rtl !== true) ? 'left' : 'right'
if (year !== this.viewModel.year) {
this.yearDirection = this.monthDirection
}
@@ -1346,7 +1420,7 @@ export default Vue.extend({
},
__emitImmediately (reason) {
- const date = this.daysModel[0] !== void 0 && this.daysModel[0].dateHash !== null
+ const date = this.daysModel[0] !== void 0 && modelIsValid(this.daysModel[0]) === true
? { ...this.daysModel[0] }
: { ...this.viewModel } // inherit day, hours, minutes, milliseconds...
@@ -1356,7 +1430,7 @@ export default Vue.extend({
date.month = this.viewModel.month
const maxDay = this.calendar !== 'persian'
- ? (new Date(date.year, date.month, 0)).getDate()
+ ? (__safeCreateDate(date.year, date.month, 0)).getDate()
: jalaaliMonthLength(date.year, date.month)
date.day = Math.min(Math.max(1, date.day), maxDay)
@@ -1402,14 +1476,18 @@ export default Vue.extend({
if (date.from !== void 0) {
// we also need to filter out intersections
- const fromHash = this.__getDayHash(date.from)
- const toHash = this.__getDayHash(date.to)
+ const fromHashInt = hashToInt(this.__getDayHash(date.from))
+ const toHashInt = hashToInt(this.__getDayHash(date.to))
const days = this.daysModel
- .filter(day => day.dateHash < fromHash || day.dateHash > toHash)
+ .filter(day => {
+ const dayHashInt = hashToInt(day.dateHash)
+
+ return dayHashInt < fromHashInt || dayHashInt > toHashInt
+ })
const ranges = this.rangeModel
- .filter(({ from, to }) => to.dateHash < fromHash || from.dateHash > toHash)
+ .filter(({ from, to }) => hashToInt(to.dateHash) < fromHashInt || hashToInt(from.dateHash) > toHashInt)
value = days.concat(ranges).concat(date).map(entry => this.__encodeEntry(entry))
}
@@ -1437,10 +1515,9 @@ export default Vue.extend({
const val = this.__encodeEntry(date)
if (date.from !== void 0) {
- model = this.value.filter(
- date => date.from !== void 0
- ? (date.from !== val.from && date.to !== val.to)
- : true
+ model = this.value.filter(date => date.from !== void 0
+ ? (date.from !== val.from && date.to !== val.to)
+ : true
)
}
else {
@@ -1459,11 +1536,10 @@ export default Vue.extend({
const model = this.daysModel
.concat(this.rangeModel)
.map(entry => this.__encodeEntry(entry, mask, locale))
- .filter(entry => {
- return entry.from !== void 0
- ? entry.from.dateHash !== null && entry.to.dateHash !== null
- : entry.dateHash !== null
- })
+ .filter(entry => entry.from !== void 0
+ ? modelIsValid(entry.from) === true && modelIsValid(entry.to) === true
+ : modelIsValid(entry)
+ )
this.$emit('input', (this.multiple === true ? model : model[0]) || null, reason)
}
diff --git a/ui/src/components/date/QDate.json b/ui/src/components/date/QDate.json
index 814c8c5f7465..e34a7570cc23 100644
--- a/ui/src/components/date/QDate.json
+++ b/ui/src/components/date/QDate.json
@@ -96,6 +96,15 @@
"category": "model"
},
+ "model-navigation": {
+ "type": [ "String", "Boolean" ],
+ "desc": "On which end of the range to navigate the calendar when the model changes; Use `false` to skip navigation",
+ "values": [ "from", "to", "(Boolean) false" ],
+ "default": "from",
+ "category": "behavior",
+ "addedIn": "v1.14.8"
+ },
+
"navigation-min-year-month": {
"type": "String",
"desc": "Lock user from navigating below a specific year+month (in YYYY/MM format); This prop is not used to correct the model; You might want to also use 'default-year-month' prop",
@@ -156,6 +165,13 @@
"addedIn": "v1.13"
},
+ "day-as-range": {
+ "type": "Boolean",
+ "desc": "Model single days as a range (object) instead of string",
+ "category": "model|selection",
+ "addedIn": "v1.15.2"
+ },
+
"emit-immediately": {
"type": "Boolean",
"desc": "Emit model when user browses month and year too; ONLY for single selection (non-multiple, non-range)",
@@ -170,6 +186,129 @@
}
},
+ "scopedSlots": {
+ "day": {
+ "desc": "Override default day content slot; Suggestion: tooltips and / or multiple event markers",
+ "scope": {
+ "i": {
+ "type": "Number",
+ "desc": "The day number in month",
+ "examples": [ 23 ]
+ },
+ "day": {
+ "type": "String",
+ "desc": "Full date in YYYY/MM/DD form",
+ "examples": [ "2020/05/20" ]
+ },
+ "fill": {
+ "type": "Boolean",
+ "desc": "The day does not belong to the currently displayed month / year (no QBtn and hidden by default)"
+ },
+ "in": {
+ "type": "Boolean",
+ "desc": "The day is selectable (QBtn is not disabled)"
+ },
+ "today": {
+ "type": "Boolean",
+ "desc": "The day is today"
+ },
+ "selected": {
+ "type": "Boolean",
+ "desc": "The day is selected"
+ },
+ "unelevated": {
+ "type": "Boolean",
+ "desc": "The day is selected"
+ },
+ "flat": {
+ "type": "Boolean",
+ "desc": "The day is not selected"
+ },
+ "classes": {
+ "type": "String",
+ "desc": "The classes applied to the day",
+ "__exemption": "examples"
+ },
+ "color": {
+ "type": "String",
+ "desc": "The color of the QBtn used for the day",
+ "examples": [ "primary" ]
+ },
+ "textColor": {
+ "type": "String",
+ "desc": "The text color of the QBtn used for the day",
+ "examples": [ "orange-2" ]
+ },
+ "event": {
+ "type": [ "Boolean", "String", "Any" ],
+ "desc": "Boolean false if there is no event for the day; The event-color or the value returned by the event-color function",
+ "examples": [ "red" ]
+ },
+ "range": {
+ "type": "Object",
+ "desc": "The range this day belongs to",
+ "definition": {
+ "from": {
+ "type": "Object",
+ "desc": "Object of properties of the range starting point (only if range)",
+ "definition": {
+ "year": {
+ "type": "Number",
+ "desc": "The year",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "month": {
+ "type": "Number",
+ "desc": "The month",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "day": {
+ "type": "Number",
+ "desc": "The day of month",
+ "__exemption": [
+ "examples"
+ ]
+ }
+ }
+ },
+ "to": {
+ "type": "Object",
+ "desc": "Object of properties of the range ending point (only if range)",
+ "definition": {
+ "year": {
+ "type": "Number",
+ "desc": "The year",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "month": {
+ "type": "Number",
+ "desc": "The month",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "day": {
+ "type": "Number",
+ "desc": "The day of month",
+ "__exemption": [
+ "examples"
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "addedIn": "v1.14.8"
+ }
+ },
+
"events": {
"input": {
"extends": "input",
@@ -306,6 +445,61 @@
"addedIn": "v1.13"
},
+ "range-change": {
+ "desc": "User has changed a range selection",
+ "params": {
+ "range": {
+ "type": "Object",
+ "desc": "Definition of the range",
+ "definition": {
+ "from": {
+ "type": "Object",
+ "desc": "Definition of date from where the range begins",
+ "definition": {
+ "year": {
+ "type": "Number",
+ "desc": "The year",
+ "__exemption": [ "examples" ]
+ },
+ "month": {
+ "type": "Number",
+ "desc": "The month",
+ "__exemption": [ "examples" ]
+ },
+ "day": {
+ "type": "Number",
+ "desc": "The day of month",
+ "__exemption": [ "examples" ]
+ }
+ }
+ },
+ "to": {
+ "type": "Object",
+ "desc": "Definition of date to where the range ends",
+ "definition": {
+ "year": {
+ "type": "Number",
+ "desc": "The year",
+ "__exemption": [ "examples" ]
+ },
+ "month": {
+ "type": "Number",
+ "desc": "The month",
+ "__exemption": [ "examples" ]
+ },
+ "day": {
+ "type": "Number",
+ "desc": "The day of month",
+ "__exemption": [ "examples" ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "addedIn": "v1.14.8"
+ },
+
"range-end": {
"desc": "User has ended a range selection",
"params": {
@@ -459,6 +653,13 @@
"__exemption": [ "examples" ]
}
}
+ },
+ "modelNavigation": {
+ "type": [ "String", "Boolean" ],
+ "desc": "On which end of the range to navigate the calendar; Use `false` to skip navigation",
+ "values": [ "from", "to", "(Boolean) false" ],
+ "default": "from",
+ "addedIn": "v1.14.8"
}
}
}
diff --git a/ui/src/components/date/QDate.sass b/ui/src/components/date/QDate.sass
index 669f7ac31283..ef4ece6da2c6 100644
--- a/ui/src/components/date/QDate.sass
+++ b/ui/src/components/date/QDate.sass
@@ -103,6 +103,12 @@
&--fill
visibility: hidden
+ min-height: 32px
+
+ &.q-date
+ &__range, &__range-from, &__range-to
+ &:before
+ opacity: .05
&__range, &__range-from, &__range-to
@@ -115,7 +121,9 @@
left: 0
right: 0
opacity: .3
+ pointer-events: none
+ &__range
&:nth-child(7n-6)
&:before
border-top-left-radius: 0
@@ -128,10 +136,15 @@
&__range-from
&:before
- left: 50%
+ left: calc(50% - 15px)
+ border-top-left-radius: $button-rounded-border-radius
+ border-bottom-left-radius: $button-rounded-border-radius
+
&__range-to
&:before
- right: 50%
+ right: calc(50% - 15px)
+ border-top-right-radius: $button-rounded-border-radius
+ border-bottom-right-radius: $button-rounded-border-radius
&__edit-range
&:after
diff --git a/ui/src/components/date/QDate.styl b/ui/src/components/date/QDate.styl
index 669f7ac31283..ef4ece6da2c6 100644
--- a/ui/src/components/date/QDate.styl
+++ b/ui/src/components/date/QDate.styl
@@ -103,6 +103,12 @@
&--fill
visibility: hidden
+ min-height: 32px
+
+ &.q-date
+ &__range, &__range-from, &__range-to
+ &:before
+ opacity: .05
&__range, &__range-from, &__range-to
@@ -115,7 +121,9 @@
left: 0
right: 0
opacity: .3
+ pointer-events: none
+ &__range
&:nth-child(7n-6)
&:before
border-top-left-radius: 0
@@ -128,10 +136,15 @@
&__range-from
&:before
- left: 50%
+ left: calc(50% - 15px)
+ border-top-left-radius: $button-rounded-border-radius
+ border-bottom-left-radius: $button-rounded-border-radius
+
&__range-to
&:before
- right: 50%
+ right: calc(50% - 15px)
+ border-top-right-radius: $button-rounded-border-radius
+ border-bottom-right-radius: $button-rounded-border-radius
&__edit-range
&:after
diff --git a/ui/src/components/time/QTime.js b/ui/src/components/time/QTime.js
index bffbc5fad3eb..423fd17e68be 100644
--- a/ui/src/components/time/QTime.js
+++ b/ui/src/components/time/QTime.js
@@ -4,7 +4,7 @@ import QBtn from '../btn/QBtn.js'
import TouchPan from '../../directives/TouchPan.js'
import { slot } from '../../utils/private/slot.js'
-import { formatDate, __splitDate } from '../../utils/date.js'
+import { formatDate, __splitDate, __safeCreateDate } from '../../utils/date.js'
import { position } from '../../utils/event.js'
import { pad } from '../../utils/format.js'
import cache from '../../utils/private/cache.js'
@@ -854,7 +854,7 @@ export default Vue.extend({
pad(date.minute) +
(this.withSeconds === true ? ':' + pad(date.second) : '')
: formatDate(
- new Date(
+ __safeCreateDate(
date.year,
date.month === null ? null : date.month - 1,
date.day,
diff --git a/ui/src/utils/date.js b/ui/src/utils/date.js
index 77eab1c62839..28de3fa77ee3 100644
--- a/ui/src/utils/date.js
+++ b/ui/src/utils/date.js
@@ -9,6 +9,7 @@ const
MILLISECONDS_IN_DAY = 86400000,
MILLISECONDS_IN_HOUR = 3600000,
MILLISECONDS_IN_MINUTE = 60000,
+ dateConstructorArgs = [ 'year', 'month', 'date', 'hours', 'minutes', 'seconds', 'milliseconds' ],
defaultMask = 'YYYY-MM-DDTHH:mm:ss.SSSZ',
token = /\[((?:[^\]\\]|\\]|\\)*)\]|d{1,4}|M{1,4}|m{1,2}|w{1,2}|Qo|Do|D{1,4}|YY(?:YY)?|H{1,2}|h{1,2}|s{1,2}|S{1,3}|Z{1,2}|a{1,2}|[AQExX]/g,
reverseToken = /(\[[^\]]*\])|d{1,4}|M{1,4}|m{1,2}|w{1,2}|Qo|Do|D{1,4}|YY(?:YY)?|H{1,2}|h{1,2}|s{1,2}|S{1,3}|Z{1,2}|a{1,2}|[AQExX]|([.*+:?^,\s${}()|\\]+)/g,
@@ -16,7 +17,7 @@ const
function getRegexData (mask, dateLocale) {
const
- days = '(' + dateLocale.days.join('|') + ')',
+ days = '(' + dateLocale.days.join('|') + '|)',
key = mask + days
if (regexStore[ key ] !== void 0) {
@@ -24,9 +25,9 @@ function getRegexData (mask, dateLocale) {
}
const
- daysShort = '(' + dateLocale.daysShort.join('|') + ')',
- months = '(' + dateLocale.months.join('|') + ')',
- monthsShort = '(' + dateLocale.monthsShort.join('|') + ')'
+ daysShort = '(' + dateLocale.daysShort.join('|') + '|)',
+ months = '(' + dateLocale.months.join('|') + '|)',
+ monthsShort = '(' + dateLocale.monthsShort.join('|') + '|)'
const map = {}
let index = 0
@@ -36,16 +37,16 @@ function getRegexData (mask, dateLocale) {
switch (match) {
case 'YY':
map.YY = index
- return '(-?\\d{1,2})'
+ return '(-?\\d{1,2}|)'
case 'YYYY':
map.YYYY = index
- return '(-?\\d{1,4})'
+ return '(-?\\d{1,4}|)'
case 'M':
map.M = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'MM':
map.M = index // bumping to M
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'MMM':
map.MMM = index
return monthsShort
@@ -54,55 +55,55 @@ function getRegexData (mask, dateLocale) {
return months
case 'D':
map.D = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'Do':
- map.D = index++ // bumping to D
- return '(\\d{1,2}(st|nd|rd|th))'
+ map.D = index // bumping to D
+ return '(\\d{1,2}(?:st|nd|rd|th)|)'
case 'DD':
map.D = index // bumping to D
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'H':
map.H = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'HH':
map.H = index // bumping to H
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'h':
map.h = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'hh':
map.h = index // bumping to h
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'm':
map.m = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'mm':
map.m = index // bumping to m
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 's':
map.s = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'ss':
map.s = index // bumping to s
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'S':
map.S = index
- return '(\\d{1})'
+ return '(\\d{1}|)'
case 'SS':
map.S = index // bump to S
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'SSS':
map.S = index // bump to S
- return '(\\d{3})'
+ return '(\\d{3}|)'
case 'A':
map.A = index
- return '(AM|PM)'
+ return '(AM|PM|)'
case 'a':
map.a = index
- return '(am|pm)'
+ return '(am|pm|)'
case 'aa':
map.aa = index
- return '(a\\.m\\.|p\\.m\\.)'
+ return '(a\\.m\\.|p\\.m\\.|)'
case 'ddd':
return daysShort
@@ -111,30 +112,30 @@ function getRegexData (mask, dateLocale) {
case 'Q':
case 'd':
case 'E':
- return '(\\d{1})'
+ return '(\\d{1}|)'
case 'Qo':
- return '(1st|2nd|3rd|4th)'
+ return '(1st|2nd|3rd|4th|)'
case 'DDD':
case 'DDDD':
- return '(\\d{1,3})'
+ return '(\\d{1,3}|)'
case 'w':
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'ww':
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'Z': // to split: (?:(Z)()()|([+-])?(\\d{2}):?(\\d{2}))
map.Z = index
- return '(Z|[+-]\\d{2}:\\d{2})'
+ return '(Z|[+-]\\d{2}:\\d{2}|)'
case 'ZZ':
map.ZZ = index
- return '(Z|[+-]\\d{2}\\d{2})'
+ return '(Z|[+-]\\d{2}\\d{2}|)'
case 'X':
map.X = index
- return '(-?\\d+)'
+ return '(-?\\d+|)'
case 'x':
map.x = index
- return '(-?\\d{4,})'
+ return '(-?\\d{4,}|)'
default:
index--
@@ -171,75 +172,6 @@ function formatTimezone (offset, delimeter = '') {
return sign + pad(hours) + delimeter + pad(minutes)
}
-function applyYearMonthDayChange (date, mod, sign) {
- let
- year = date.getFullYear(),
- month = date.getMonth()
-
- const day = date.getDate()
-
- if (mod.year !== void 0) {
- year += sign * mod.year
- delete mod.year
- }
-
- if (mod.month !== void 0) {
- month += sign * mod.month
- delete mod.month
- }
-
- date.setDate(1)
- date.setMonth(2)
-
- date.setFullYear(year)
- date.setMonth(month)
- date.setDate(Math.min(day, daysInMonth(date)))
-
- if (mod.date !== void 0) {
- date.setDate(date.getDate() + sign * mod.date)
- delete mod.date
- }
-
- return date
-}
-
-function applyYearMonthDay (date, mod, middle) {
- const
- year = mod.year !== void 0 ? mod.year : date[ `get${middle}FullYear` ](),
- month = mod.month !== void 0 ? mod.month - 1 : date[ `get${middle}Month` ](),
- maxDay = (new Date(year, month + 1, 0)).getDate(),
- day = Math.min(maxDay, mod.date !== void 0 ? mod.date : date[ `get${middle}Date` ]())
-
- date[ `set${middle}Date` ](1)
- date[ `set${middle}Month` ](2)
-
- date[ `set${middle}FullYear` ](year)
- date[ `set${middle}Month` ](month)
- date[ `set${middle}Date` ](day)
-
- delete mod.year
- delete mod.month
- delete mod.date
-
- return date
-}
-
-function getChange (date, rawMod, sign) {
- const
- mod = normalizeMod(rawMod),
- d = new Date(date),
- t = mod.year !== void 0 || mod.month !== void 0 || mod.date !== void 0
- ? applyYearMonthDayChange(d, mod, sign) // removes year/month/day
- : d
-
- for (const key in mod) {
- const op = capitalize(key)
- t[ `set${op}` ](t[ `get${op}` ]() + sign * mod[ key ])
- }
-
- return t
-}
-
function normalizeMod (mod) {
const acc = { ...mod }
@@ -285,18 +217,106 @@ function normalizeMod (mod) {
return acc
}
-export function adjustDate (date, rawMod, utc) {
+const maxDayDate = new Date()
+function getMaxDay (year, month, calendar) {
+ if (calendar === 'persian') {
+ return jalaaliMonthLength(year, month)
+ }
+
+ maxDayDate.setFullYear(year)
+ maxDayDate.setMonth(month)
+ maxDayDate.setDate(0)
+ return maxDayDate.getDate()
+}
+
+function applyYearMonthDay (date, mod, type) {
+ const
+ year = mod.year !== void 0 ? mod.year : date[ `get${type}FullYear` ](),
+ month = mod.month !== void 0 ? mod.month - 1 : date[ `get${type}Month` ](),
+ maxDay = getMaxDay(year, month + 1),
+ day = Math.max(1, Math.min(maxDay, mod.date !== void 0 ? mod.date : date[ `get${type}Date` ]()))
+
+ date[ `set${type}Date` ](1)
+ date[ `set${type}Month` ](2)
+
+ date[ `set${type}FullYear` ](year)
+ date[ `set${type}Month` ](month)
+ date[ `set${type}Date` ](day)
+
+ delete mod.year
+ delete mod.month
+ delete mod.date
+
+ return date
+}
+
+function applyYearMonthDayChange (date, mod, sign) {
+ let
+ year = date.getFullYear(),
+ month = date.getMonth() + 1
+
+ if (mod.year !== void 0) {
+ year += sign * mod.year
+ delete mod.year
+ }
+
+ if (mod.month !== void 0) {
+ month += sign * mod.month
+ delete mod.month
+ }
+
+ applyYearMonthDay(date, { year, month }, '')
+
+ if (mod.date !== void 0) {
+ date.setDate(date.getDate() + sign * mod.date)
+ delete mod.date
+ }
+
+ return date
+}
+
+function getChange (date, rawMod, sign) {
const
mod = normalizeMod(rawMod),
- middle = utc === true ? 'UTC' : '',
d = new Date(date),
t = mod.year !== void 0 || mod.month !== void 0 || mod.date !== void 0
- ? applyYearMonthDay(d, mod, middle) // removes year/month/day
+ ? applyYearMonthDayChange(d, mod, sign) // removes year/month/day
+ : d
+
+ for (const key in mod) {
+ const op = capitalize(key)
+ t[ `set${op}` ](t[ `get${op}` ]() + sign * mod[ key ])
+ }
+
+ return t
+}
+
+export function __safeCreateDate (...args) {
+ if (args.length > 1 && args[0] >= 0 && args[0] <= 99) {
+ return buildDate(dateConstructorArgs.reduce((mod, key, index) => {
+ if (args[index] !== void 0) {
+ mod[key] = args[index]
+ }
+
+ return mod
+ }, {}))
+ }
+
+ return new Date(...args)
+}
+
+export function adjustDate (date, rawMod, utc) {
+ const
+ mod = normalizeMod(rawMod),
+ type = utc === true ? 'UTC' : '',
+ d = date === null || date === void 0 ? new Date() : new Date(date),
+ t = mod.year !== void 0 || mod.month !== void 0 || mod.date !== void 0
+ ? applyYearMonthDay(d, mod, type) // removes year/month/day
: d
for (const key in mod) {
const op = key.charAt(0).toUpperCase() + key.slice(1)
- t[ `set${middle}${op}` ](mod[ key ])
+ t[ `set${type}${op}` ](mod[ key ])
}
return t
@@ -305,7 +325,7 @@ export function adjustDate (date, rawMod, utc) {
export function extractDate (str, mask, dateLocale) {
const d = __splitDate(str, mask, dateLocale)
- const date = new Date(
+ const date = __safeCreateDate(
d.year,
d.month === null ? null : d.month - 1,
d.day === null ? 1 : d.day,
@@ -364,16 +384,23 @@ export function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
return date
}
+ const idx = { ...map }
+ Object.keys(idx).forEach(key => {
+ if (match[idx[key]] === '') {
+ idx[key] = void 0
+ }
+ })
+
let tzString = ''
- if (map.X !== void 0 || map.x !== void 0) {
- const stamp = parseInt(match[ map.X !== void 0 ? map.X : map.x ], 10)
+ if (idx.X !== void 0 || idx.x !== void 0) {
+ const stamp = parseInt(match[ idx.X !== void 0 ? idx.X : idx.x ], 10)
if (isNaN(stamp) === true || stamp < 0) {
return date
}
- const d = new Date(stamp * (map.X !== void 0 ? 1000 : 1))
+ const d = new Date(stamp * (idx.X !== void 0 ? 1000 : 1))
date.year = d.getFullYear()
date.month = d.getMonth() + 1
@@ -384,77 +411,73 @@ export function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
date.millisecond = d.getMilliseconds()
}
else {
- if (map.YYYY !== void 0) {
- date.year = parseInt(match[ map.YYYY ], 10)
+ if (idx.YYYY !== void 0) {
+ date.year = parseInt(match[ idx.YYYY ], 10)
}
- else if (map.YY !== void 0) {
- const y = parseInt(match[ map.YY ], 10)
+ else if (idx.YY !== void 0) {
+ const y = parseInt(match[ idx.YY ], 10)
date.year = y < 0 ? y : 2000 + y
}
- if (map.M !== void 0) {
- date.month = parseInt(match[ map.M ], 10)
+ if (idx.M !== void 0) {
+ date.month = parseInt(match[ idx.M ], 10)
if (date.month < 1 || date.month > 12) {
return date
}
}
- else if (map.MMM !== void 0) {
- date.month = monthsShort.indexOf(match[ map.MMM ]) + 1
+ else if (idx.MMM !== void 0) {
+ date.month = monthsShort.indexOf(match[ idx.MMM ]) + 1
}
- else if (map.MMMM !== void 0) {
- date.month = months.indexOf(match[ map.MMMM ]) + 1
+ else if (idx.MMMM !== void 0) {
+ date.month = months.indexOf(match[ idx.MMMM ]) + 1
}
- if (map.D !== void 0) {
- date.day = parseInt(match[ map.D ], 10)
+ if (idx.D !== void 0) {
+ date.day = parseInt(match[ idx.D ], 10)
if (date.year === null || date.month === null || date.day < 1) {
return date
}
- const maxDay = calendar !== 'persian'
- ? (new Date(date.year, date.month, 0)).getDate()
- : jalaaliMonthLength(date.year, date.month)
-
- if (date.day > maxDay) {
+ if (date.day > getMaxDay(date.year, date.month, calendar)) {
return date
}
}
- if (map.H !== void 0) {
- date.hour = parseInt(match[ map.H ], 10) % 24
+ if (idx.H !== void 0) {
+ date.hour = parseInt(match[ idx.H ], 10) % 24
}
- else if (map.h !== void 0) {
- date.hour = parseInt(match[ map.h ], 10) % 12
+ else if (idx.h !== void 0) {
+ date.hour = parseInt(match[ idx.h ], 10) % 12
if (
- (map.A && match[ map.A ] === 'PM') ||
- (map.a && match[ map.a ] === 'pm') ||
- (map.aa && match[ map.aa ] === 'p.m.')
+ (idx.A && match[ idx.A ] === 'PM') ||
+ (idx.a && match[ idx.a ] === 'pm') ||
+ (idx.aa && match[ idx.aa ] === 'p.m.')
) {
date.hour += 12
}
date.hour = date.hour % 24
}
- if (map.m !== void 0) {
- date.minute = parseInt(match[ map.m ], 10) % 60
+ if (idx.m !== void 0) {
+ date.minute = parseInt(match[ idx.m ], 10) % 60
}
- if (map.s !== void 0) {
- date.second = parseInt(match[ map.s ], 10) % 60
+ if (idx.s !== void 0) {
+ date.second = parseInt(match[ idx.s ], 10) % 60
}
- if (map.S !== void 0) {
- date.millisecond = parseInt(match[ map.S ], 10) * 10 ** (3 - match[ map.S ].length)
+ if (idx.S !== void 0) {
+ date.millisecond = parseInt(match[ idx.S ], 10) * 10 ** (3 - match[ idx.S ].length)
}
- if (map.Z !== void 0 || map.ZZ !== void 0) {
- tzString = (map.Z !== void 0 ? match[ map.Z ].replace(':', '') : match[ map.ZZ ])
+ if (idx.Z !== void 0 || idx.ZZ !== void 0) {
+ tzString = (idx.Z !== void 0 ? match[ idx.Z ].replace(':', '') : match[ idx.ZZ ])
date.timezoneOffset = (tzString[ 0 ] === '+' ? -1 : 1) * (60 * tzString.slice(1, 3) + 1 * tzString.slice(3, 5))
}
}
- date.dateHash = pad(date.year, 6) + '/' + pad(date.month) + '/' + pad(date.day)
+ date.dateHash = date.year + '/' + pad(date.month) + '/' + pad(date.day)
date.timeHash = pad(date.hour) + ':' + pad(date.minute) + ':' + pad(date.second) + tzString
return date
@@ -467,23 +490,24 @@ export function isValid (date) {
}
export function buildDate (mod, utc) {
- return adjustDate(new Date(), mod, utc)
+ return adjustDate(null, mod, utc)
}
-export function getDayOfWeek (date) {
+export function getDayOfWeek (date, dateLocale) {
+ const langOpts = getDateLocale(dateLocale, lang.props)
const dow = new Date(date).getDay()
- return dow === 0 ? 7 : dow
+ return 1 + (7 + dow - (langOpts.firstDayOfWeek || 0)) % 7
}
export function getWeekOfYear (date) {
// Remove time components of date
- const thursday = new Date(date.getFullYear(), date.getMonth(), date.getDate())
+ const thursday = __safeCreateDate(date.getFullYear(), date.getMonth(), date.getDate())
// Change date to Thursday same week
thursday.setDate(thursday.getDate() - ((thursday.getDay() + 6) % 7) + 3)
// Take January 4th as it is always in week 1 (see ISO 8601)
- const firstThursday = new Date(thursday.getFullYear(), 0, 4)
+ const firstThursday = __safeCreateDate(thursday.getFullYear(), 0, 4)
// Change date to Thursday same week
firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3)
@@ -598,10 +622,12 @@ export function getMinDate (date /*, ...args */) {
}
function getDiff (t, sub, interval) {
- return (
- (t.getTime() - t.getTimezoneOffset() * MILLISECONDS_IN_MINUTE) -
- (sub.getTime() - sub.getTimezoneOffset() * MILLISECONDS_IN_MINUTE)
- ) / interval
+ return Math.floor(
+ (
+ (t.getTime() - t.getTimezoneOffset() * MILLISECONDS_IN_MINUTE) -
+ (sub.getTime() - sub.getTimezoneOffset() * MILLISECONDS_IN_MINUTE)
+ ) / interval
+ )
}
export function getDateDiff (date, subtract, unit = 'days') {
@@ -717,7 +743,7 @@ export function isSameDate (date, date2, unit) {
}
export function daysInMonth (date) {
- return (new Date(date.getFullYear(), date.getMonth() + 1, 0)).getDate()
+ return (__safeCreateDate(date.getFullYear(), date.getMonth() + 1, 0)).getDate()
}
function getOrdinal (n) {
@@ -734,16 +760,16 @@ function getOrdinal (n) {
const formatter = {
// Year: 00, 01, ..., 99
- YY (date, _, forcedYear) {
+ YY (date, _dateLocale, forcedYear) {
// workaround for < 1900 with new Date()
- const y = this.YYYY(date, _, forcedYear) % 100
+ const y = this.YYYY(date, _dateLocale, forcedYear) % 100
return y >= 0
? pad(y)
: '-' + pad(Math.abs(y))
},
// Year: 1900, 1901, ..., 2099
- YYYY (date, _, forcedYear) {
+ YYYY (date, _dateLocale, forcedYear) {
// workaround for < 1900 with new Date()
return forcedYear !== void 0 && forcedYear !== null
? forcedYear
@@ -853,13 +879,9 @@ const formatter = {
// Hour: 1, 2, ..., 12
h (date) {
const hours = date.getHours()
- if (hours === 0) {
- return 12
- }
- if (hours > 12) {
- return hours % 12
- }
- return hours
+ return hours === 0
+ ? 12
+ : (hours > 12 ? hours % 12 : hours)
},
// Hour: 01, 02, ..., 12
@@ -918,7 +940,7 @@ const formatter = {
},
// Timezone: -01:00, +00:00, ... +12:00
- Z (date, dateLocale, forcedYear, forcedTimezoneOffset) {
+ Z (date, _dateLocale, _forcedYear, forcedTimezoneOffset) {
const tzOffset = forcedTimezoneOffset === void 0 || forcedTimezoneOffset === null
? date.getTimezoneOffset()
: forcedTimezoneOffset
@@ -927,7 +949,7 @@ const formatter = {
},
// Timezone: -0100, +0000, ... +1200
- ZZ (date, dateLocale, forcedYear, forcedTimezoneOffset) {
+ ZZ (date, _dateLocale, _forcedYear, forcedTimezoneOffset) {
const tzOffset = forcedTimezoneOffset === void 0 || forcedTimezoneOffset === null
? date.getTimezoneOffset()
: forcedTimezoneOffset
@@ -969,11 +991,9 @@ export function formatDate (val, mask, dateLocale, __forcedYear, __forcedTimezon
return mask.replace(
token,
- (match, text) => (
- match in formatter
- ? formatter[ match ](date, locale, __forcedYear, __forcedTimezoneOffset)
- : (text === void 0 ? match : text.split('\\]').join(']'))
- )
+ (match, text) => match in formatter
+ ? formatter[ match ](date, locale, __forcedYear, __forcedTimezoneOffset)
+ : (text === void 0 ? match : text.split('\\]').join(']'))
)
}
diff --git a/ui/types/utils/date.d.ts b/ui/types/utils/date.d.ts
index 535e6e589f4a..22176c450e2b 100644
--- a/ui/types/utils/date.d.ts
+++ b/ui/types/utils/date.d.ts
@@ -21,6 +21,7 @@ export interface DateLocale {
daysShort?: string[];
months?: string[];
monthsShort?: string[];
+ firstDayOfWeek?: number;
}
export type DateUnitOptions =
@@ -42,7 +43,7 @@ export namespace date {
function isValid(date: number | string): boolean;
function extractDate(str: string, mask: string, locale?: DateLocale): Date;
function buildDate(options: DateOptions, utc?: boolean): Date;
- function getDayOfWeek(date: Date): number;
+ function getDayOfWeek(date: Date | number | string, locale?: DateLocale): number;
function getWeekOfYear(date: Date | number | string): number;
function isBetweenDates(date: Date | number | string, from: Date | number | string, to: Date | number | string, opts?: { inclusiveFrom: boolean; inclusiveTo: boolean; onlyDate: boolean }): boolean;
function addToDate(date: Date | number | string, options: DateOptions): Date;