Upstream spencermountain/spacetime state of the touched files before d9eaced13f

This commit is contained in:
upstream committed 2026-09-04 09:43:57 +02:00
commit 96f6f12e4e
3 files changed
+7931

No files matched your search

+7415
View File
File diff suppressed because it is too large. Load diff
+276
View File
@@ -0,0 +1,276 @@
// javascript setX methods like setDate() can't be used because of the local bias
//these methods wrap around them.
import ms from '../../data/milliseconds.js'
import { mapping } from '../../data/months.js'
import walkTo from './walk.js'
import { isLeapYear } from '../../fns.js'
import { getMonthLength } from './_model.js'
const validate = (n) => {
//handle number as a string
if (typeof n === 'string') {
n = parseInt(n, 10)
}
return n
}
const order = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond']
//reduce hostile micro-changes when moving dates by millisecond
const confirm = (s, tmp, unit) => {
const n = order.indexOf(unit)
const arr = order.slice(n, order.length)
for (let i = 0; i < arr.length; i++) {
const want = tmp[arr[i]]()
s[arr[i]](want)
}
return s
}
// allow specifying setter direction
const fwdBkwd = function (s, old, goFwd, unit) {
if (goFwd === true && s.isBefore(old)) {
s = s.add(1, unit)
} else if (goFwd === false && s.isAfter(old)) {
s = s.minus(1, unit)
}
return s
}
const milliseconds = function (s, n) {
n = validate(n)
const current = s.millisecond()
const diff = current - n //milliseconds to shift by
return s.epoch - diff
}
const seconds = function (s, n, goFwd) {
n = validate(n)
const old = s.clone()
const diff = s.second() - n
const shift = diff * ms.second
s.epoch = s.epoch - shift
s = fwdBkwd(s, old, goFwd, 'minute') // specify direction
return s.epoch
}
const minutes = function (s, n, goFwd) {
n = validate(n)
const old = s.clone()
const diff = s.minute() - n
const shift = diff * ms.minute
s.epoch -= shift
confirm(s, old, 'second')
s = fwdBkwd(s, old, goFwd, 'hour') // specify direction
return s.epoch
}
const hours = function (s, n, goFwd) {
n = validate(n)
if (n >= 24) {
n = 24
} else if (n < 0) {
n = 0
}
const old = s.clone()
let diff = s.hour() - n
let shift = diff * ms.hour
s.epoch -= shift
// oops, did we change the day?
if (s.date() !== old.date()) {
s = old.clone()
if (diff > 1) {
diff -= 1
}
if (diff < 1) {
diff += 1
}
shift = diff * ms.hour
s.epoch -= shift
}
walkTo(s, {
hour: n
})
confirm(s, old, 'minute')
s = fwdBkwd(s, old, goFwd, 'day') // specify direction
return s.epoch
}
const time = function (s, str, goFwd) {
let m = str.match(/([0-9]{1,2})[:h]([0-9]{1,2})(:[0-9]{1,2})? ?(am|pm)?/)
if (!m) {
//fallback to support just '2am'
m = str.match(/([0-9]{1,2}) ?(am|pm)/)
if (!m) {
return null // invalid time string - mark as invalid
}
m.splice(2, 0, '0') //add implicit 0 minutes
m.splice(3, 0, '') //add implicit seconds
}
let h24 = false
let hour = parseInt(m[1], 10)
let minute = parseInt(m[2], 10)
if (minute >= 60) {
minute = 59
}
if (hour > 12) {
h24 = true
}
//make the hour into proper 24h time
if (h24 === false) {
if (m[4] === 'am' && hour === 12) {
//12am is midnight
hour = 0
}
if (m[4] === 'pm' && hour < 12) {
//12pm is noon
hour += 12
}
}
// handle seconds
m[3] = m[3] || ''
m[3] = m[3].replace(/:/, '')
const sec = parseInt(m[3], 10) || 0
const old = s.clone()
s = s.hour(hour)
s = s.minute(minute)
s = s.second(sec)
s = s.millisecond(0)
s = fwdBkwd(s, old, goFwd, 'day') // specify direction
return s.epoch
}
const date = function (s, n, goFwd) {
n = validate(n)
//avoid setting february 31st (leap-aware, same as add())
if (n > 28) {
const max = getMonthLength(s.month(), s.year())
if (n > max) {
n = max
}
}
//avoid setting < 0
if (n <= 0) {
n = 1
}
const old = s.clone()
walkTo(s, {
date: n
})
s = fwdBkwd(s, old, goFwd, 'month') // specify direction
return s.epoch
}
const month = function (s, n, goFwd) {
if (typeof n === 'string') {
if (n === 'sept') {
n = 'sep'
}
n = mapping()[n.toLowerCase()]
}
n = validate(n)
//don't go past december
if (n >= 12) {
n = 11
}
if (n <= 0) {
n = 0
}
let d = s.date()
//there's no 30th of february, etc. (leap-aware, same as add())
const max = getMonthLength(n, s.year())
if (d > max) {
//make it as close as we can..
d = max
}
const old = s.clone()
walkTo(s, {
month: n,
date: d
})
s = fwdBkwd(s, old, goFwd, 'year') // specify direction
return s.epoch
}
const year = function (s, n) {
// support '97
if (typeof n === 'string' && /^'[0-9]{2}$/.test(n)) {
n = n.replace(/'/, '').trim()
n = Number(n)
// '89 is 1989
if (n > 30) {
//change this in 10y
n = 1900 + n
} else {
// '12 is 2012
n = 2000 + n
}
}
n = validate(n)
walkTo(s, {
year: n
})
return s.epoch
}
const week = function (s, n, goFwd) {
const old = s.clone()
n = validate(n)
// don't set week=1, if we're already week=1 in late december
// because this could send us to another year
if (n === 1) {
if (s.monthName() === 'december' && s.date() >= 29) {
if (s.week() === 1) {
return s
}
}
}
// get the first week of the year
s = s.month(0)
s = s.date(1)
s = s.day('monday', false)//go backwards to monday
// did we go back too far?
if (s.monthName() === 'december' && s.date() < 29) {
s = s.add(1, 'week')
}
n -= 1 //1-based
s = s.add(n, 'weeks')
s = fwdBkwd(s, old, goFwd, 'year') // specify direction
return s
}
const dayOfYear = function (s, n, goFwd) {
n = validate(n)
const old = s.clone()
n -= 1 //days are 1-based
if (n <= 0) {
n = 0
} else if (n >= 365) {
if (isLeapYear(s.year())) {
n = 365
} else {
n = 364
}
}
s = s.startOf('year')
s = s.add(n, 'day')
confirm(s, old, 'hour')
s = fwdBkwd(s, old, goFwd, 'year') // specify direction
return s.epoch
}
export {
milliseconds,
seconds,
minutes,
hours,
time,
date,
month,
year,
week,
dayOfYear,
}
+240
View File
@@ -0,0 +1,240 @@
import test from 'tape'
import spacetime from './lib/index.js'
test('set', (t) => {
let s = spacetime('June 22, 2017 20:12:01', 'Canada/Pacific')
s = s.date(5)
t.equal(s.date(), 5, '.date(5)')
s = s.date('24')
t.equal(s.date(), 24, '.date-string-int')
s = s.date(12)
t.equal(s.date(), 12, '.date()')
s = s.year(2015)
t.equal(s.year(), 2015, '.year()')
s = s.hour(5)
t.equal(s.hour(), 5, '.hour()')
s = s.minute(8)
t.equal(s.minute(), 8, '.minute()')
s = s.month('february')
t.equal(s.month(), 1, '.month()')
s = s.month('June')
t.equal(s.monthName(), 'june', '.month()')
s = s.month('apr')
t.equal(s.monthName(), 'april', '.month()')
s = s.week(1) //mon dec 29th
t.equal(s.monthName(), 'december', '.week()')
s = s.quarter(1)
t.equal(s.quarter(), 1, '.quarter()')
s = s.hourFloat(7.25)
t.equal(s.hour(), 7, '.hour()')
t.equal(s.minute(), 15, '.minute()')
s = s.dayOfYear(15)
t.equal(s.monthName(), 'january', 'dayOfYear-.month()')
t.equal(s.date(), 15, '.dayofYear-date')
s = s.month(1)
t.equal(s.monthName(), 'february', '.monthNum()')
s = s.season('summer')
t.equal(s.monthName(), 'june', 'season-.month()')
t.equal(s.date(), 1, 'season-.date()')
s = s.hour(7)
s = s.ampm('am')
t.equal(s.ampm(), 'am', 'ampm-already-ampm()')
t.equal(s.hour(), 7, 'ampm-already-hour()')
s = s.ampm('pm')
t.equal(s.hour(), 19, 'ampm-hour()')
t.equal(s.ampm(), 'pm', 'ampm-ampm()')
s = s.time('5:25')
t.equal(s.hour(), 5, 'time-hour()')
t.equal(s.minute(), 25, 'time-minute()')
s = s.time('5:20pm')
t.equal(s.hour(), 17, 'time-hour-pm()')
t.equal(s.minute(), 20, 'time-minute-pm()')
s = s.time('5:20 pm')
t.equal(s.hour(), 17, 'time-hour-pm-with-space()')
t.equal(s.minute(), 20, 'time-minute-pm-with-space()')
s = s.time('5pm')
t.equal(s.hour(), 17, 'time-hour-pm-hour-only()')
t.equal(s.minute(), 0, 'time-minute-pm-hour-only()')
s = s.time('6 pm')
t.equal(s.hour(), 18, 'time-hour-pm-hour-only-with-space()')
t.equal(s.minute(), 0, 'time-minute-pm-hour-only-with-space()')
s = s.time('13:20pm')
t.equal(s.hour(), 13, 'time-hour-24h()')
t.equal(s.minute(), 20, 'time-minute-24h()')
t.equal(s.era(), 'AD', '2017 ad')
s = s.era('bc')
t.equal(s.era(), 'BC', '2015 bc')
// t.equal(s.year(), -2015, '-2015') //may be broken?
t.end()
})
test('set by weekday', (t) => {
let s = spacetime([2017, 2, 22], 'Canada/Eastern') //wednesday
//make sure it's in the right place
t.equal(s.date(), 22, '.date()')
t.equal(s.dayName(), 'wednesday', '.day()')
s = s.day('thursday')
t.equal(s.date(), 23, 'now thursday')
s = s.day('friday')
t.equal(s.date(), 24, 'now friday')
s = s.day('sat')
t.equal(s.date(), 25, 'now saturday')
s = s.day('sunday')
//never click-into the next week
t.equal(s.date(), 19, 'now sunday')
s = s.day('monday')
t.equal(s.date(), 20, 'now monday')
s = s.day(2)
t.equal(s.date(), 21, 'now tuesday')
t.end()
})
test('set-tricky', (t) => {
let s = spacetime('June 22, 2017 13:01:00', 'Canada/Eastern') //the 22rd
t.equal(s.date(), 22, '.date()')
t.equal(s.hour(), 13, '.hour()')
t.equal(s.ampm(), 'pm', 'night-here')
s = s.goto('Australia/Brisbane') //the 23rd
t.equal(s.date(), 23, 'tomorrow-there')
t.equal(s.ampm(), 'am', 'am-there')
//make it 1oclock
s = s.hour(13)
t.equal(s.hour(), 13, '.hour-remote')
//make it 1:30pm
s = s.minute(30)
t.equal(s.minute(), 30, '.minute-remote')
//make it the 5th (not working)
s = s.date(5)
t.equal(s.date(), 5, 'set-date-remotely')
t.end()
})
test('rollback-in-constructor', (t) => {
let s = spacetime('June 22, 2017 5:01:00', 'Australia/Brisbane')
t.equal(s.hour(), 5, 'hour-australia')
s = spacetime('June 22, 2017 5:01:00', 'Canada/Pacific')
t.equal(s.hour(), 5, 'hour-pacific')
s = spacetime('June 22, 2017 5:01:00', 'Canada/Eastern')
t.equal(s.hour(), 5, 'hour-eastern')
t.end()
})
test('set-date-fancy', (t) => {
let s = spacetime('June 22, 2017 20:01:00', 'Australia/Brisbane') //the 22rd
t.equal(s.date(), 22, '.date-before')
s = s.date(15)
t.equal(s.date(), 15, '.date-after')
t.end()
})
test('military time format', (t) => {
let s = spacetime('2018-10-21')
s = s.time('13h00')
t.equal(s.time(), '1:00pm', '13h00')
s = s.time('2h30')
t.equal(s.time(), '2:30am', '2h30')
t.end()
})
test('add-a-week-bug', (t) => {
const a = spacetime('2018-10-21').goto('America/Adak').add(1, 'week')
const b = spacetime('2018-10-22').goto('America/Adak').add(1, 'week')
t.notEqual(a.format('iso'), b.format('iso'), 'two days are not the same')
t.equal(a.diff(b, 'day'), 1, 'still one day apart')
t.end()
})
test('set time', (t) => {
const a = spacetime('2018-10-21').time('5:00pm').time('3:90pm')
t.equal(a.time(), '3:59pm', 'dont overflow minutes in time format')
t.end()
})
test('invalid time parsing', (t) => {
// Invalid time formats should return isValid() === false
let s = spacetime.now()
s = s.time('abc')
t.equal(s.isValid(), false, 'abc is invalid')
s = spacetime.now().time('2')
t.equal(s.isValid(), false, '2 (no am/pm) is invalid')
s = spacetime.now().time('hello world')
t.equal(s.isValid(), false, 'hello world is invalid')
s = spacetime.now().time('')
t.equal(s.isValid(), false, 'empty string is invalid')
s = spacetime.now().time('not a time')
t.equal(s.isValid(), false, 'not a time is invalid')
t.end()
})
test('time parsing edge cases', (t) => {
let s = spacetime.now()
s = s.time('12am')
t.equal(s.isValid(), true, '12am is valid')
t.equal(s.hour(), 0, '12am is midnight (hour 0)')
s = spacetime.now().time('12pm')
t.equal(s.isValid(), true, '12pm is valid')
t.equal(s.hour(), 12, '12pm is noon (hour 12)')
s = spacetime.now().time('1:00am')
t.equal(s.isValid(), true, '1:00am is valid')
s = spacetime.now().time('11:59pm')
t.equal(s.isValid(), true, '11:59pm is valid')
t.end()
})
test('month()/date() setters clamp the day to the month length', (t) => {
const iso = (s) => s.format('iso-short')
// .month() must clamp the day to the target month's length
t.equal(iso(spacetime('2020-01-31').month('april')), '2020-04-30', 'jan 31 -> april clamps to the 30th')
t.equal(iso(spacetime('2020-01-31').month('june')), '2020-06-30', 'jan 31 -> june clamps to the 30th')
// leap-year february
t.equal(iso(spacetime('2020-01-31').month('february')), '2020-02-29', 'jan 31 -> feb 29 in a leap year')
t.equal(iso(spacetime('2024-01-31').month('february')), '2024-02-29', 'jan 31 -> feb 29 in a leap year')
t.equal(iso(spacetime('2021-01-31').month('february')), '2021-02-28', 'jan 31 -> feb 28 in a common year')
// .date() must clamp to the same leap-aware length
t.equal(iso(spacetime('2020-02-10').date(31)), '2020-02-29', 'date(31) in a leap february clamps to the 29th')
t.equal(iso(spacetime('2020-02-10').date(30)), '2020-02-29', 'date(30) in a leap february clamps to the 29th')
t.equal(iso(spacetime('2021-02-10').date(31)), '2021-02-28', 'date(31) in a common february clamps to the 28th')
t.end()
})