package ut import ( "errors" "fmt" "strings" "time" ) // // Era denotes the Era types // type Era uint8 // // Era types // const ( // AD Era = iota // BC // ) const ( am = "am" pm = "pm" ) // Standard Formats for Dates, Times & DateTimes // These are the options to pass to the Format method. const ( DateFormatFull = iota DateFormatLong DateFormatMedium DateFormatShort TimeFormatFull TimeFormatLong TimeFormatMedium TimeFormatShort DateTimeFormatFull DateTimeFormatLong DateTimeFormatMedium DateTimeFormatShort ) // Characters with special meaning in a datetime string: // Technically, all a-z,A-Z characters should be treated as if they represent a // datetime unit - but not all actually do. Any a-z,A-Z character that is // intended to be rendered as a literal a-z,A-Z character should be surrounded // by single quotes. There is currently no support for rendering a single quote // literal. const ( datetimeFormatUnitEra = 'G' datetimeFormatUnitYear = 'y' datetimeFormatUnitMonth = 'M' datetimeFormatUnitDayOfWeek = 'E' datetimeFormatUnitDay = 'd' datetimeFormatUnitHour12 = 'h' datetimeFormatUnitHour24 = 'H' datetimeFormatUnitMinute = 'm' datetimeFormatUnitSecond = 's' datetimeFormatUnitPeriod = 'a' datetimeForamtUnitQuarter = 'Q' datetimeFormatUnitTimeZone1 = 'z' datetimeFormatUnitTimeZone2 = 'v' datetimeFormatTimeSeparator = ':' datetimeFormatLiteral = '\'' ) // The sequence length of datetime unit characters indicates how they should be // rendered. const ( datetimeFormatLength1Plus = 1 datetimeFormatLength2Plus = 2 datetimeFormatLengthAbbreviated = 3 datetimeFormatLengthWide = 4 datetimeFormatLengthNarrow = 5 ) // datetime formats are a sequences off datetime components and string literals const ( datetimePatternComponentUnit = iota datetimePatternComponentLiteral ) // A list of currently unsupported units: // These still need to be implemented. For now they are ignored. var ( datetimeFormatUnitCutset = []rune{ datetimeForamtUnitQuarter, datetimeFormatUnitTimeZone1, datetimeFormatUnitTimeZone2, } ) type datetimePatternComponent struct { pattern string componentType int } // FmtDateFull formats the time with the current locales full date format func (c Calendar) FmtDateFull(t time.Time) string { if t.Year() < 0 { return c.Format(t, c.Formats.DateEra.BC.Full) } return c.Format(t, c.Formats.DateEra.AD.Full) } // FmtDateLong formats the time with the current locales long date format func (c Calendar) FmtDateLong(t time.Time) string { if t.Year() < 0 { return c.Format(t, c.Formats.DateEra.BC.Long) } return c.Format(t, c.Formats.DateEra.AD.Long) } // FmtDateMedium formats the time with the current locales medium date format func (c Calendar) FmtDateMedium(t time.Time) string { if t.Year() < 0 { return c.Format(t, c.Formats.DateEra.BC.Medium) } return c.Format(t, c.Formats.DateEra.AD.Medium) } // FmtDateShort formats the time with the current locales short date format func (c Calendar) FmtDateShort(t time.Time) string { if t.Year() < 0 { return c.Format(t, c.Formats.DateEra.BC.Short) } return c.Format(t, c.Formats.DateEra.AD.Short) } // FmtTimeFull formats the time with the current locales full time format func (c Calendar) FmtTimeFull(t time.Time) string { return c.Format(t, c.Formats.Time.Full) } // FmtTimeLong formats the time with the current locales long time format func (c Calendar) FmtTimeLong(t time.Time) string { return c.Format(t, c.Formats.Time.Long) } // FmtTimeMedium formats the time with the current locales medium time format func (c Calendar) FmtTimeMedium(t time.Time) string { return c.Format(t, c.Formats.Time.Medium) } // FmtTimeShort formats the time with the current locales short time format func (c Calendar) FmtTimeShort(t time.Time) string { return c.Format(t, c.Formats.Time.Short) } // FmtDateTimeFull formats the time with the current locales full data & time format func (c Calendar) FmtDateTimeFull(t time.Time) string { dt := c.Formats.DateEra.AD.Full if t.Year() < 0 { dt = c.Formats.DateEra.BC.Full } pattern := getDateTimePattern(c.Formats.DateTime.Full, dt, c.Formats.Time.Full) return c.Format(t, pattern) } // FmtDateTimeLong formats the time with the current locales long data & time format func (c Calendar) FmtDateTimeLong(t time.Time) string { dt := c.Formats.DateEra.AD.Long if t.Year() < 0 { dt = c.Formats.DateEra.BC.Long } pattern := getDateTimePattern(c.Formats.DateTime.Long, dt, c.Formats.Time.Long) return c.Format(t, pattern) } // FmtDateTimeMedium formats the time with the current locales medium data & time format func (c Calendar) FmtDateTimeMedium(t time.Time) string { dt := c.Formats.DateEra.AD.Medium if t.Year() < 0 { dt = c.Formats.DateEra.BC.Medium } pattern := getDateTimePattern(c.Formats.DateTime.Medium, dt, c.Formats.Time.Medium) return c.Format(t, pattern) } // FmtDateTimeShort formats the time with the current locales short data & time format func (c Calendar) FmtDateTimeShort(t time.Time) string { dt := c.Formats.DateEra.AD.Short if t.Year() < 0 { dt = c.Formats.DateEra.BC.Short } pattern := getDateTimePattern(c.Formats.DateTime.Short, dt, c.Formats.Time.Short) return c.Format(t, pattern) } // FmtDateFullSafe formats the time with the current locales full date format func (c Calendar) FmtDateFullSafe(t time.Time) (string, error) { if t.Year() < 0 { return c.FormatSafe(t, c.Formats.DateEra.BC.Full) } return c.FormatSafe(t, c.Formats.DateEra.AD.Full) } // FmtDateLongSafe formats the time with the current locales long date format func (c Calendar) FmtDateLongSafe(t time.Time) (string, error) { if t.Year() < 0 { return c.FormatSafe(t, c.Formats.DateEra.BC.Long) } return c.FormatSafe(t, c.Formats.DateEra.AD.Long) } // FmtDateMediumSafe formats the time with the current locales medium date format func (c Calendar) FmtDateMediumSafe(t time.Time) (string, error) { if t.Year() < 0 { return c.FormatSafe(t, c.Formats.DateEra.BC.Medium) } return c.FormatSafe(t, c.Formats.DateEra.AD.Medium) } // FmtDateShortSafe formats the time with the current locales short date format func (c Calendar) FmtDateShortSafe(t time.Time) (string, error) { if t.Year() < 0 { return c.FormatSafe(t, c.Formats.DateEra.BC.Short) } return c.FormatSafe(t, c.Formats.DateEra.AD.Short) } // FmtTimeFullSafe formats the time with the current locales full time format func (c Calendar) FmtTimeFullSafe(t time.Time) (string, error) { return c.FormatSafe(t, c.Formats.Time.Full) } // FmtTimeLongSafe formats the time with the current locales long time format func (c Calendar) FmtTimeLongSafe(t time.Time) (string, error) { return c.FormatSafe(t, c.Formats.Time.Long) } // FmtTimeMediumSafe formats the time with the current locales medium time format func (c Calendar) FmtTimeMediumSafe(t time.Time) (string, error) { return c.FormatSafe(t, c.Formats.Time.Medium) } // FmtTimeShortSafe formats the time with the current locales short time format func (c Calendar) FmtTimeShortSafe(t time.Time) (string, error) { return c.FormatSafe(t, c.Formats.Time.Short) } // FmtDateTimeFullSafe formats the time with the current locales full data & time format func (c Calendar) FmtDateTimeFullSafe(t time.Time) (string, error) { dt := c.Formats.DateEra.AD.Full if t.Year() < 0 { dt = c.Formats.DateEra.BC.Full } pattern := getDateTimePattern(c.Formats.DateTime.Full, dt, c.Formats.Time.Full) return c.FormatSafe(t, pattern) } // FmtDateTimeLongSafe formats the time with the current locales long data & time format func (c Calendar) FmtDateTimeLongSafe(t time.Time) (string, error) { dt := c.Formats.DateEra.AD.Long if t.Year() < 0 { dt = c.Formats.DateEra.BC.Long } pattern := getDateTimePattern(c.Formats.DateTime.Long, dt, c.Formats.Time.Long) return c.FormatSafe(t, pattern) } // FmtDateTimeMediumSafe formats the time with the current locales medium data & time format func (c Calendar) FmtDateTimeMediumSafe(t time.Time) (string, error) { dt := c.Formats.DateEra.AD.Medium if t.Year() < 0 { dt = c.Formats.DateEra.BC.Medium } pattern := getDateTimePattern(c.Formats.DateTime.Medium, dt, c.Formats.Time.Medium) return c.FormatSafe(t, pattern) } // FmtDateTimeShortSafe formats the time with the current locales short data & time format func (c Calendar) FmtDateTimeShortSafe(t time.Time) (string, error) { dt := c.Formats.DateEra.AD.Short if t.Year() < 0 { dt = c.Formats.DateEra.BC.Short } pattern := getDateTimePattern(c.Formats.DateTime.Short, dt, c.Formats.Time.Short) return c.FormatSafe(t, pattern) } // FormatSafe takes a time struct and a format and returns a formatted // string. Callers should use a DateFormat, TimeFormat, or DateTimeFormat // constant. func (c Calendar) FormatSafe(datetime time.Time, pattern string) (string, error) { parsed, err := c.parseDateTimeFormat(pattern) if err != nil { return "", err } return c.formatDateTime(datetime, parsed) } // Format takes a time struct and a format and returns a formatted // string. Callers should use a DateFormat, TimeFormat, or DateTimeFormat // constant. // NOTE: this function returns blank when an error occurs func (c Calendar) Format(datetime time.Time, pattern string) string { dt, err := c.FormatSafe(datetime, pattern) if err != nil { return "" } return dt } // formatDateTime takes a time.Time and a sequence of parsed pattern components // and returns an internationalized string representation. func (c Calendar) formatDateTime(datetime time.Time, pattern []*datetimePatternComponent) (string, error) { formatted := "" for _, component := range pattern { if component.componentType == datetimePatternComponentLiteral { formatted += component.pattern } else { f, err := c.formatDateTimeComponent(datetime, component.pattern) if err != nil { return "", err } formatted += f } } return strings.Trim(formatted, " ,"), nil } // formatDateTimeComponent renders a single component of a datetime format // pattern. func (c Calendar) formatDateTimeComponent(datetime time.Time, pattern string) (string, error) { switch pattern[0:1] { case string(datetimeFormatUnitEra): return c.formatDateTimeComponentEra(datetime, len(pattern)) case string(datetimeFormatUnitYear): return c.formatDateTimeComponentYear(datetime, len(pattern)) case string(datetimeFormatUnitMonth): return c.formatDateTimeComponentMonth(datetime, len(pattern)) case string(datetimeFormatUnitDayOfWeek): return c.formatDateTimeComponentDayOfWeek(datetime, len(pattern)) case string(datetimeFormatUnitDay): return c.formatDateTimeComponentDay(datetime, len(pattern)) case string(datetimeFormatUnitHour12): return c.formatDateTimeComponentHour12(datetime, len(pattern)) case string(datetimeFormatUnitHour24): return c.formatDateTimeComponentHour24(datetime, len(pattern)) case string(datetimeFormatUnitMinute): return c.formatDateTimeComponentMinute(datetime, len(pattern)) case string(datetimeFormatUnitSecond): return c.formatDateTimeComponentSecond(datetime, len(pattern)) case string(datetimeFormatUnitPeriod): return c.formatDateTimeComponentPeriod(datetime, len(pattern)) case string(datetimeForamtUnitQuarter): return c.formatDateTimeComponentQuarter(datetime, len(pattern)) case string(datetimeFormatUnitTimeZone1): fallthrough case string(datetimeFormatUnitTimeZone2): return c.formatDateTimeComponentTimeZone(datetime, len(pattern)) } return "", errors.New("unknown datetime format unit: " + pattern[0:1]) } // formatDateTimeComponentEra renders an era component. // TODO: not yet implemented func (c Calendar) formatDateTimeComponentEra(datetime time.Time, length int) (string, error) { if datetime.Year() < 0 { switch length { case datetimeFormatLength1Plus: return c.FormatNames.Eras.BC.Narrow, nil case datetimeFormatLength2Plus: return c.FormatNames.Eras.BC.Abbrev, nil case datetimeFormatLengthWide: return c.FormatNames.Eras.BC.Full, nil default: return "", nil } } switch length { case datetimeFormatLength1Plus: return c.FormatNames.Eras.AD.Narrow, nil case datetimeFormatLength2Plus: return c.FormatNames.Eras.AD.Abbrev, nil case datetimeFormatLengthWide: return c.FormatNames.Eras.AD.Full, nil default: return "", nil } } // formatDateTimeComponentYear renders a year component. func (c Calendar) formatDateTimeComponentYear(datetime time.Time, length int) (string, error) { year := datetime.Year() if year < 0 { year = year * -1 } switch length { case datetimeFormatLength1Plus: return c.formatDateTimeComponentYearLengthWide(year), nil case datetimeFormatLength2Plus: return c.formatDateTimeComponentYearLength2Plus(year), nil case datetimeFormatLengthWide: return c.formatDateTimeComponentYearLengthWide(year), nil } return "", fmt.Errorf("unsupported year length: %d", length) } // formatDateTimeComponentYearLength2Plus renders a 2-digit year component. func (c Calendar) formatDateTimeComponentYearLength2Plus(year int) string { yearShort := year % 100 if yearShort < 10 { return fmt.Sprintf("0%d", yearShort) } return fmt.Sprintf("%d", yearShort) } // formatDateTimeComponentYearLength2Plus renders a full-year component - for // all modern dates, that's four digits. func (c Calendar) formatDateTimeComponentYearLengthWide(year int) string { return fmt.Sprintf("%d", year) } // formatDateTimeComponentMonth renders a month component. func (c Calendar) formatDateTimeComponentMonth(datetime time.Time, length int) (string, error) { month := datetime.Month() switch length { case datetimeFormatLength1Plus: return c.formatDateTimeComponentMonth1Plus(month), nil case datetimeFormatLength2Plus: return c.formatDateTimeComponentMonth2Plus(month), nil case datetimeFormatLengthAbbreviated: return c.formatDateTimeComponentMonthAbbreviated(month), nil case datetimeFormatLengthWide: return c.formatDateTimeComponentMonthWide(month), nil case datetimeFormatLengthNarrow: return c.formatDateTimeComponentMonthNarrow(month), nil } return "", fmt.Errorf("unsupported month length: %d", length) } // formatDateTimeComponentMonth1Plus renders a numeric month component with 1 or // 2 digits depending on value. func (c Calendar) formatDateTimeComponentMonth1Plus(month time.Month) string { return fmt.Sprintf("%d", month) } // formatDateTimeComponentMonth2Plus renders a numeric month component always // with 2 digits. func (c Calendar) formatDateTimeComponentMonth2Plus(month time.Month) string { if month < 10 { return fmt.Sprintf("0%d", month) } return fmt.Sprintf("%d", month) } // formatDateTimeComponentMonthAbbreviated renders an abbreviated text month // component. func (c Calendar) formatDateTimeComponentMonthAbbreviated(month time.Month) string { return c.FormatNames.Months.Abbreviated[month] } // formatDateTimeComponentMonthWide renders a full text month component. func (c Calendar) formatDateTimeComponentMonthWide(month time.Month) string { return c.FormatNames.Months.Wide[month] } // formatDateTimeComponentMonthNarrow renders a super-short month compontent - // not guaranteed to be unique for different months. func (c Calendar) formatDateTimeComponentMonthNarrow(month time.Month) string { return c.FormatNames.Months.Narrow[month] } // formatDateTimeComponentDayOfWeek renders a day-of-week component. func (c Calendar) formatDateTimeComponentDayOfWeek(datetime time.Time, length int) (string, error) { switch length { case datetimeFormatLength1Plus: return c.formatDateTimeComponentDayOfWeekWide(datetime.Weekday()), nil case datetimeFormatLength2Plus: return c.formatDateTimeComponentDayOfWeekShort(datetime.Weekday()), nil case datetimeFormatLengthAbbreviated: return c.formatDateTimeComponentDayOfWeekAbbreviated(datetime.Weekday()), nil case datetimeFormatLengthWide: return c.formatDateTimeComponentDayOfWeekWide(datetime.Weekday()), nil case datetimeFormatLengthNarrow: return c.formatDateTimeComponentDayOfWeekNarrow(datetime.Weekday()), nil } return "", fmt.Errorf("unsupported year day-of-week: %d", length) } // formatDateTimeComponentDayOfWeekAbbreviated renders an abbreviated text // day-of-week component. func (c Calendar) formatDateTimeComponentDayOfWeekAbbreviated(weekday time.Weekday) string { return c.FormatNames.Days.Abbreviated[weekday] } // formatDateTimeComponentDayOfWeekAbbreviated renders a // shorter-then-abbreviated but still unique text day-of-week component. func (c Calendar) formatDateTimeComponentDayOfWeekShort(weekday time.Weekday) string { return c.FormatNames.Days.Short[weekday] } // formatDateTimeComponentDayOfWeekWide renders a full text day-of-week // component. func (c Calendar) formatDateTimeComponentDayOfWeekWide(weekday time.Weekday) string { return c.FormatNames.Days.Wide[weekday] } // formatDateTimeComponentDayOfWeekNarrow renders a super-short day-of-week // compontent - not guaranteed to be unique for different days. func (c Calendar) formatDateTimeComponentDayOfWeekNarrow(weekday time.Weekday) string { return c.FormatNames.Days.Narrow[weekday] } // formatDateTimeComponentDay renders a day-of-year component. func (c Calendar) formatDateTimeComponentDay(datetime time.Time, length int) (string, error) { day := datetime.Day() switch length { case datetimeFormatLength1Plus: return fmt.Sprintf("%d", day), nil case datetimeFormatLength2Plus: if day < 10 { return fmt.Sprintf("0%d", day), nil } return fmt.Sprintf("%d", day), nil } return "", fmt.Errorf("unsupported day-of-year: %d", length) } // formatDateTimeComponentHour12 renders an hour-component using a 12-hour // clock. func (c Calendar) formatDateTimeComponentHour12(datetime time.Time, length int) (string, error) { hour := datetime.Hour() if hour > 12 { hour = hour - 12 } switch length { case datetimeFormatLength1Plus: return fmt.Sprintf("%d", hour), nil case datetimeFormatLength2Plus: if hour < 10 { return fmt.Sprintf("0%d", hour), nil } return fmt.Sprintf("%d", hour), nil } return "", fmt.Errorf("unsupported hour-12: %d", length) } // formatDateTimeComponentHour24 renders an hour-component using a 24-hour // clock. func (c Calendar) formatDateTimeComponentHour24(datetime time.Time, length int) (string, error) { hour := datetime.Hour() switch length { case datetimeFormatLength1Plus: return fmt.Sprintf("%d", hour), nil case datetimeFormatLength2Plus: if hour < 10 { return fmt.Sprintf("0%d", hour), nil } return fmt.Sprintf("%d", hour), nil } return "", fmt.Errorf("unsupported hour-24: %d", length) } // formatDateTimeComponentMinute renders a minute component. func (c Calendar) formatDateTimeComponentMinute(datetime time.Time, length int) (string, error) { minute := datetime.Minute() switch length { case datetimeFormatLength1Plus: return fmt.Sprintf("%d", minute), nil case datetimeFormatLength2Plus: if minute < 10 { return fmt.Sprintf("0%d", minute), nil } return fmt.Sprintf("%d", minute), nil } return "", fmt.Errorf("unsupported minute: %d", length) } // formatDateTimeComponentSecond renders a second component func (c Calendar) formatDateTimeComponentSecond(datetime time.Time, length int) (string, error) { second := datetime.Second() switch length { case datetimeFormatLength1Plus: return fmt.Sprintf("%d", second), nil case datetimeFormatLength2Plus: if second < 10 { return fmt.Sprintf("0%d", second), nil } return fmt.Sprintf("%d", second), nil } return "", fmt.Errorf("unsupported second: %d", length) } // formatDateTimeComponentPeriod renders a period component (AM/PM). func (c Calendar) formatDateTimeComponentPeriod(datetime time.Time, length int) (string, error) { hour := datetime.Hour() switch length { case datetimeFormatLength1Plus: return c.formatDateTimeComponentPeriodWide(hour), nil case datetimeFormatLengthAbbreviated: return c.formatDateTimeComponentPeriodAbbreviated(hour), nil case datetimeFormatLengthWide: return c.formatDateTimeComponentPeriodWide(hour), nil case datetimeFormatLengthNarrow: return c.formatDateTimeComponentPeriodNarrow(hour), nil } return "", fmt.Errorf("unsupported day-period: %d", length) } // formatDateTimeComponentPeriodAbbreviated renders an abbreviated period // component. func (c Calendar) formatDateTimeComponentPeriodAbbreviated(hour int) string { if hour < 12 { return c.FormatNames.Periods.Abbreviated[am] } return c.FormatNames.Periods.Abbreviated[pm] } // formatDateTimeComponentPeriodWide renders a full period component. func (c Calendar) formatDateTimeComponentPeriodWide(hour int) string { if hour < 12 { return c.FormatNames.Periods.Wide[am] } return c.FormatNames.Periods.Wide[pm] } // formatDateTimeComponentPeriodNarrow renders a super-short period component. func (c Calendar) formatDateTimeComponentPeriodNarrow(hour int) string { if hour < 12 { return c.FormatNames.Periods.Narrow[am] } return c.FormatNames.Periods.Narrow[pm] } // formatDateTimeComponentQuarter renders a calendar quarter component - this // is calendar quarters and not fiscal quarters. // - Q1: Jan-Mar // - Q2: Apr-Jun // - Q3: Jul-Sep // - Q4: Oct-Dec // TODO: not yet implemented func (c Calendar) formatDateTimeComponentQuarter(datetime time.Time, length int) (string, error) { return "", nil } // formatDateTimeComponentTimeZone renders a time zone component. // TODO: this has not yet been implemented func (c Calendar) formatDateTimeComponentTimeZone(datetime time.Time, length int) (string, error) { return "", nil } // parseDateTimeFormat takes a format pattern string and returns a sequence of // components. func (c Calendar) parseDateTimeFormat(pattern string) ([]*datetimePatternComponent, error) { // every thing between single quotes should become a literal // all non a-z, A-Z characters become a literal // everything else, repeat character sequences become a component format := []*datetimePatternComponent{} for i := 0; i < len(pattern); { char := pattern[i : i+1] skip := false // for units we don't support yet, just skip over them for _, r := range datetimeFormatUnitCutset { if char == string(r) { skip = true break } } if skip { i++ continue } if char == string(datetimeFormatLiteral) { // find the next single quote // create a literal out of everything between the quotes // and set i to the position after the second quote if i == len(pattern)-1 { return []*datetimePatternComponent{}, errors.New("malformed datetime format") } nextQuote := strings.Index(pattern[i+1:], string(datetimeFormatLiteral)) if nextQuote == -1 { return []*datetimePatternComponent{}, errors.New("malformed datetime format") } component := &datetimePatternComponent{ pattern: pattern[i+1 : nextQuote+i+1], componentType: datetimePatternComponentLiteral, } format = append(format, component) i = nextQuote + i + 2 continue } if (char >= "a" && char <= "z") || (char >= "A" && char <= "Z") { // this represents a format unit // find the entire sequence of the same character endChar := lastSequenceIndex(pattern[i:]) + i component := &datetimePatternComponent{ pattern: pattern[i : endChar+1], componentType: datetimePatternComponentUnit, } format = append(format, component) i = endChar + 1 continue } if char == string(datetimeFormatTimeSeparator) { component := &datetimePatternComponent{ // pattern: c.TimeSeparator, pattern: string(datetimeFormatTimeSeparator), componentType: datetimePatternComponentLiteral, } format = append(format, component) i++ continue } component := &datetimePatternComponent{ pattern: char, componentType: datetimePatternComponentLiteral, } format = append(format, component) i++ continue } return format, nil } // getDateTimePattern combines a date pattern and a time pattern into a datetime // pattern. The datetimePattern argument includes a {0} placeholder for the time // pattern, and a {1} placeholder for the date component. func getDateTimePattern(datetimePattern, datePattern, timePattern string) string { return strings.Replace(strings.Replace(datetimePattern, "{1}", datePattern, 1), "{0}", timePattern, 1) } // lastSequenceIndex looks at the first character in a string and returns the // last digits of the first sequence of that character. For example: // - ABC: 0 // - AAB: 1 // - ABA: 0 // - AAA: 2 func lastSequenceIndex(str string) int { if len(str) == 0 { return -1 } if len(str) == 1 { return 0 } sequenceChar := str[0:1] lastPos := 0 for i := 1; i < len(str); i++ { if str[i:i+1] != sequenceChar { break } lastPos = i } return lastPos }