-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdate-fns.ts
182 lines (157 loc) · 5.41 KB
/
date-fns.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// import { flow } from 'fp-ts/lib/function'
import { formatWithOptions, isMatch, parseWithOptions, isValid } from 'date-fns/fp'
import { parseISO } from 'date-fns'
import ja from 'date-fns/locale/ja'
import { nowInJapan } from 'src/utils/common'
import * as R from 'ramda'
import { either as E, function as F } from 'fp-ts'
import { format as formatTZ } from 'date-fns-tz'
export const isISO = F.flow(parseISO, isValid)
// const TIME_ZONE_TOKYO = 'Asia/Tokyo'
export const DATE_FORMAT = 'yyyy/MM/dd'
const format = formatWithOptions({ locale: ja })
export const formatISO8601JST = (d: Date): string =>
formatTZ(d, "yyyy-MM-dd'T'HH:mm:ssXXX", { timeZone: 'Asia/Tokyo' })
const twoDigit = (s: string | number): string => ('0' + s).slice(-2)
export const getUTCDate = (y: number | string, m: number | string, d: number | string): Date =>
parseISO(`${y}-${twoDigit(m)}-${twoDigit(d)}T00:00:00.000Z`)
export const getDatePlus9TZ = (y: number | string, m: number | string, d: number | string): Date =>
parseISO(`${y}-${twoDigit(m)}-${twoDigit(d)}T00:00:00+09:00`)
// https://date-fns.org/v2.16.1/docs/format
export const timestampStr = (): string => format('yyMMddhhmmssSSS', nowInJapan())
// https://github.com/date-fns/date-fns/blob/master/src/fp/formatWithOptions/index.js
/**
* 動作環境のタイムゾーンを考慮した出力を行う(具体的には以下参照)
*
* ```javascript
* const date = _.getDatePlus9(1991, 10, 6)
* const uctDate = _.getUTCDate(1991, 10, 6)
* expect(date.toISOString()).toBe('1991-10-05T15:00:00.000Z')
* expect(uctDate.toISOString()).toBe('1991-10-06T00:00:00.000Z')
* expect(_.jsDate2format(date)).toBe('1991/10/06')
* ```
*/
export const date2string: (date: Date) => string = format(DATE_FORMAT)
// https://github.com/date-fns/date-fns/blob/master/src/fp/isMatch/index.js
export const isValidDateString = isMatch(DATE_FORMAT)
// https://date-fns.org/v2.16.1/docs/parse
export const dateString2jsDate = parseWithOptions({ locale: ja }, 0, DATE_FORMAT)
// narrow, short, long は Intl.DateTimeFormat のマネ
// https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_(%E6%97%A5%E6%9C%AC)
export const eraInfo = [
{
era: '令和',
short: '令',
narrow: 'R',
timestamp: getDatePlus9TZ(2019, 5, 1),
startYear: 2019,
lastYear: undefined,
startDay: '05/01',
lastDay: undefined,
},
{
era: '平成',
short: '平',
narrow: 'H',
timestamp: getDatePlus9TZ(1989, 1, 8),
startYear: 1989,
lastYear: '31',
startDay: '01/08',
lastDay: '04/30',
},
{
era: '昭和',
short: '昭',
narrow: 'S',
timestamp: getDatePlus9TZ(1926, 12, 25),
startYear: 1926,
lastYear: '64',
startDay: '12/25',
lastDay: '01/07',
},
{
era: '大正',
short: '大',
narrow: 'T',
timestamp: getDatePlus9TZ(1912, 7, 30),
startYear: 1912,
lastYear: '15',
startDay: '07/30',
lastDay: '12/24',
},
{
era: '明治',
short: '明',
narrow: 'M',
timestamp: getDatePlus9TZ(1868, 1, 25),
startYear: 1868,
lastYear: '45',
startDay: '01/25',
lastDay: '07/29',
},
] as const
const eraLetters = R.pluck('narrow', eraInfo)
type EraLetters = typeof eraLetters[number]
const separator = '[/.]?'
const number = '\\d{1,2}'
/**
* e.g.) "H01/01/01", "S1/1/1"
* TODO short も対応させる
*/
const narrowFormatRegExp = new RegExp(
`^([${eraLetters.join('|')}])(${number})${separator}(${number})${separator}(${number})$`
)
export const findEraInfoByEra = (era: EraLetters) => R.find(R.propEq('narrow', era), eraInfo)
export const findEraInfoByStartYear = (startYear: number) =>
R.find(R.propEq('startYear', startYear), eraInfo)
/**
* string( N00/00/00 ) -> Date
* 修正は行わない
*/
export const parseJpDateString = (s: string): E.Either<string, Date> => {
const data = s.match(narrowFormatRegExp)
if (!data) return E.left('形式不一致')
const [_, era, y_, m_, d_] = data
const [yy, mm, dd] = [y_, m_, d_].map(twoDigit)
const i = findEraInfoByEra(era as EraLetters)
if (i === undefined) return E.left('eraInfo または narrowFormatRegExp の設定ミス')
if (yy === '01' && `${mm}/${dd}` < i.startDay) return E.left('初日未満')
if (i.lastYear && yy > i.lastYear) return E.left('年数超過')
if (i.lastYear && i.lastDay && yy === i.lastYear && `${mm}/${dd}` > i.lastDay)
return E.left('最終日超過')
const yyyy = String(i.startYear - 1 + Number(yy))
return E.right(getDatePlus9TZ(yyyy, mm, dd))
}
/**
* Date -> string( N00.00.00 )
*/
export const dateToJpStr =
(format = 'nyy.mm.dd') =>
(d: Date): string => {
for (let i = 0; i < eraInfo.length; i++) {
const info = eraInfo[i]
if (d.getTime() >= info.timestamp.getTime()) {
const [y_, m_, d_] = [
d.getFullYear() - (info.startYear - 1),
d.getMonth() + 1,
d.getDate(),
].map(String)
let ret = format
ret = ret.replace(/n/g, info.narrow)
ret = ret.replace(/N/g, info.era)
ret = ret.replace(/yy/g, twoDigit(y_))
ret = ret.replace(/y/g, y_)
ret = ret.replace(/mm/g, twoDigit(m_))
ret = ret.replace(/y/g, m_)
ret = ret.replace(/dd/g, twoDigit(d_))
ret = ret.replace(/d/g, d_)
return ret
}
}
return ''
}
/*
https://ja.wikipedia.org/wiki/ISO_8601#%E6%97%A5%E6%9C%AC_(JIS_X_0301)
上記URLによれば, メタ文字は「N」
また, セパレータは 「.」 が標準
*/