-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from rabee-inc/feature/time-formatter
feat: timefmt.TimeFormatter を実装
- Loading branch information
Showing
3 changed files
with
1,005 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package timefmt | ||
|
||
import "time" | ||
|
||
type TimeFormatter interface { | ||
// dayjs like format | ||
// REF: https://day.js.org/docs/en/display/format | ||
// ISO8601 format example: "YYYY-MM-DDTHH:mm:ss.SSSZ" | ||
// escape character: []. | ||
// escape example: | ||
// - "[Today is] YYYY/MM/DD" = "Today is 2024/01/01" | ||
// - not escape "Today is YYYY/MM/DD" = "To1amy i0 2024/01/01" | ||
// YYYY: 4 digit year (2024) | ||
// YY: 2 digit year (24) | ||
// MMMM: full month name (January) | ||
// MMM: short month name (Jan) | ||
// MM: 2 digit month (01-12) | ||
// M: 1 digit month (1-12) | ||
// DD: 2 digit day (01-31) | ||
// D: 1 digit day (1-31) | ||
// HH: 2 digit hour (00-23) | ||
// H: 1 digit hour (0-23) | ||
// hh: 2 digit hour (00-12) | ||
// h: 1 digit hour (0-12) | ||
// mm: 2 digit minute (00-59) | ||
// m: 1 digit minute (0-59) | ||
// ss: 2 digit second (00-59) | ||
// s: 1 digit second (0-59) | ||
// SSS: 3 digit millisecond (000-999) | ||
// A: upper case meridiem (AM/PM) | ||
// a: lower case meridiem (am/pm) | ||
// Z: time zone offset (+09:00/Z). if time zone is UTC, return "Z". | ||
// ZZ: time zone offset (+0900/Z). if time zone is UTC, return "Z". | ||
// dddd: full day of the week (Sunday) | ||
// ddd: short day of the week (Sun) | ||
// dd: min day of the week (Su) | ||
// d: 1 digit day of the week (0-6) | ||
Format(t time.Time, layout string) string | ||
|
||
// dddd で使用される曜日の full name を設定する。 | ||
// len(names) != 7 の場合 panic。 | ||
// ex) []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} | ||
// ex) []string{"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"} | ||
SetWeekdayFullNames(names []string) | ||
|
||
// dddd で使用される曜日の short name を取得する | ||
WeekdayFullNames() []string | ||
|
||
// ddd で使用される曜日の short name を設定する。 | ||
// len(names) != 7 の場合 panic。 | ||
// ex) []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} | ||
// ex) []string{"日", "月", "火", "水", "木", "金", "土"} | ||
SetWeekdayShortNames(names []string) | ||
|
||
// ddd で使用される曜日の short name を取得する。 | ||
WeekdayShortNames() []string | ||
|
||
// dd で使用される曜日の min name を設定する。 | ||
// len(names) != 7 の場合 panic。 | ||
// ex) []string{"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} | ||
// ex) []string{"日", "月", "火", "水", "木", "金", "土"} | ||
SetWeekdayMinNames(names []string) | ||
|
||
// dd で使用される曜日の min name を取得する。 | ||
WeekdayMinNames() []string | ||
|
||
// MMMM で使用される month の name を設定する。 | ||
// len(names) != 12 の場合 panic。 | ||
// ex) []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} | ||
// ex) []string{"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"} | ||
SetMonthNames(names []string) | ||
|
||
// MMMM で使用される month の name を取得する | ||
MonthNames() []string | ||
|
||
// MMM で使用される month の short name を設定する。 | ||
// len(names) != 12 の場合 panic。 | ||
// ex) []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} | ||
// ex) []string{"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"} | ||
SetMonthShortNames(names []string) | ||
|
||
// MMM で使用される month の short name を取得する | ||
MonthShortNames() []string | ||
|
||
// AM/PM を判定する関数を設定する。 | ||
// hours には 24 時間表記の時間が入る。(0〜23) | ||
SetMeridiem(f func(hours int) string) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
package timefmt | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type timeFormatter struct { | ||
weekdayFullNames []string | ||
weekdayShortNames []string | ||
weekdayMinNames []string | ||
monthNames []string | ||
monthShortNames []string | ||
meridiemFunc func(int) string | ||
} | ||
|
||
var defaultWeekdayFullNames = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} | ||
var defaultWeekdayShortNames = []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} | ||
var defaultWeekdayMinNames = []string{"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} | ||
var defaultMonthNames = []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} | ||
var defaultMonthShortNames = []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} | ||
var defaultMeridiemFunc = func(hours int) string { | ||
if hours < 12 { | ||
return "am" | ||
} | ||
return "pm" | ||
} | ||
|
||
// REF: https://github.com/iamkun/dayjs/blob/dev/src/constant.js#L30C30-L30C112 | ||
var reFormat = regexp.MustCompile(`\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS`) | ||
|
||
var weekLen = 7 | ||
var monthLen = 12 | ||
|
||
// New returns a new instance of TimeFormatter. | ||
func New() TimeFormatter { | ||
return &timeFormatter{ | ||
weekdayFullNames: defaultWeekdayFullNames, | ||
weekdayShortNames: defaultWeekdayShortNames, | ||
weekdayMinNames: defaultWeekdayMinNames, | ||
monthNames: defaultMonthNames, | ||
monthShortNames: defaultMonthShortNames, | ||
meridiemFunc: nil, | ||
} | ||
} | ||
|
||
func (t *timeFormatter) Format(tm time.Time, layout string) string { | ||
return reFormat.ReplaceAllStringFunc(layout, func(s string) string { | ||
// [] で囲まれている場合は、フォーマットせずに[]の中身を返す | ||
if s[0] == '[' { | ||
return s[1 : len(s)-1] | ||
} | ||
return t.format(tm, s) | ||
}) | ||
} | ||
|
||
func (t *timeFormatter) MonthNames() []string { | ||
return t.monthNames | ||
} | ||
|
||
func (t *timeFormatter) MonthShortNames() []string { | ||
return t.monthShortNames | ||
} | ||
|
||
func (t *timeFormatter) SetMonthNames(names []string) { | ||
mustLen(names, monthLen) | ||
t.monthNames = names | ||
} | ||
|
||
func (t *timeFormatter) SetMonthShortNames(names []string) { | ||
mustLen(names, monthLen) | ||
t.monthShortNames = names | ||
} | ||
|
||
func (t *timeFormatter) SetWeekdayFullNames(names []string) { | ||
mustLen(names, weekLen) | ||
t.weekdayFullNames = names | ||
} | ||
|
||
func (t *timeFormatter) SetWeekdayMinNames(names []string) { | ||
mustLen(names, weekLen) | ||
t.weekdayMinNames = names | ||
} | ||
|
||
func (t *timeFormatter) SetWeekdayShortNames(names []string) { | ||
mustLen(names, weekLen) | ||
t.weekdayShortNames = names | ||
} | ||
|
||
func (t *timeFormatter) WeekdayFullNames() []string { | ||
return t.weekdayFullNames | ||
} | ||
|
||
func (t *timeFormatter) WeekdayMinNames() []string { | ||
return t.weekdayMinNames | ||
} | ||
|
||
func (t *timeFormatter) WeekdayShortNames() []string { | ||
return t.weekdayShortNames | ||
} | ||
|
||
func (t *timeFormatter) SetMeridiem(f func(hours int) string) { | ||
t.meridiemFunc = f | ||
} | ||
|
||
// mustLen ... names の長さが l でない場合 panic にする | ||
func mustLen(names []string, l int) { | ||
if len(names) != l { | ||
panic(fmt.Sprintf("len error: names length must be %d but %d", l, len(names))) | ||
} | ||
} | ||
|
||
// chunk に応じたフォーマットを行う | ||
func (t *timeFormatter) format(tm time.Time, chunk string) string { | ||
switch chunk { | ||
// year | ||
case "YYYY": | ||
return strconv.Itoa(tm.Year()) | ||
case "YY": | ||
return fmt.Sprintf("%02d", tm.Year()%100) | ||
|
||
// month | ||
case "M": | ||
return strconv.Itoa(int(tm.Month())) | ||
case "MM": | ||
return fmt.Sprintf("%02d", int(tm.Month())) | ||
case "MMM": | ||
return t.monthShortNames[int(tm.Month())-1] | ||
case "MMMM": | ||
return t.monthNames[int(tm.Month())-1] | ||
|
||
// day | ||
case "D": | ||
return strconv.Itoa(tm.Day()) | ||
case "DD": | ||
return fmt.Sprintf("%02d", tm.Day()) | ||
|
||
// hours | ||
case "H": | ||
return strconv.Itoa(tm.Hour()) | ||
case "HH": | ||
return fmt.Sprintf("%02d", tm.Hour()) | ||
case "h", "hh": | ||
h := tm.Hour() % 12 | ||
if h == 0 { | ||
h = 12 | ||
} | ||
if chunk == "h" { | ||
return strconv.Itoa(h) | ||
} | ||
return fmt.Sprintf("%02d", h) | ||
|
||
// minutes | ||
case "m": | ||
return strconv.Itoa(tm.Minute()) | ||
case "mm": | ||
return fmt.Sprintf("%02d", tm.Minute()) | ||
|
||
// seconds | ||
case "s": | ||
return strconv.Itoa(tm.Second()) | ||
case "ss": | ||
return fmt.Sprintf("%02d", tm.Second()) | ||
|
||
// milliseconds | ||
case "SSS": | ||
return fmt.Sprintf("%03d", tm.Nanosecond()/1e6) | ||
|
||
// AM/PM | ||
case "a", "A": | ||
meridiemFunc := t.meridiemFunc | ||
if meridiemFunc == nil { | ||
meridiemFunc = defaultMeridiemFunc | ||
} | ||
meridiem := meridiemFunc(tm.Hour()) | ||
if chunk == "a" { | ||
return strings.ToLower(meridiem) | ||
} | ||
return strings.ToUpper(meridiem) | ||
|
||
// weekday | ||
case "d": | ||
return strconv.Itoa(int(tm.Weekday())) | ||
case "dd": | ||
return t.weekdayMinNames[int(tm.Weekday())] | ||
case "ddd": | ||
return t.weekdayShortNames[int(tm.Weekday())] | ||
case "dddd": | ||
return t.weekdayFullNames[int(tm.Weekday())] | ||
|
||
// timezone | ||
case "Z", "ZZ": | ||
_, offset := tm.Zone() | ||
if offset == 0 { | ||
return "Z" | ||
} | ||
sign := "+" | ||
if offset < 0 { | ||
sign = "-" | ||
offset = -offset | ||
} | ||
hour := offset / 3600 | ||
min := (offset % 3600) / 60 | ||
if chunk == "Z" { | ||
return fmt.Sprintf("%s%02d:%02d", sign, hour, min) | ||
} | ||
return fmt.Sprintf("%s%02d%02d", sign, hour, min) | ||
|
||
// match しない場合はそのまま返す | ||
default: | ||
return chunk | ||
} | ||
} |
Oops, something went wrong.