time · math · math/rand 패키지 — 시간, 수학, 난수
시간 처리, 수학 계산, 난수 생성은 거의 모든 프로그램에서 필요한 기능입니다. Go 표준 라이브러리는 이 세 가지를 위한 강력한 패키지를 제공합니다.
time 패키지 — 시간 처리의 모든 것
현재 시간과 기본 조작
package main
import (
"fmt"
"time"
)
func main() {
// 현재 시간
now := time.Now()
fmt.Println(now) // 2024-01-15 10:30:00.123456789 +0900 KST
// 시간 구성 요소
fmt.Println("연도:", now.Year())
fmt.Println("월:", now.Month()) // January
fmt.Println("월(숫자):", int(now.Month())) // 1
fmt.Println("일:", now.Day())
fmt.Println("시:", now.Hour())
fmt.Println("분:", now.Minute())
fmt.Println("초:", now.Second())
fmt.Println("나노초:", now.Nanosecond())
fmt.Println("요일:", now.Weekday()) // Monday
// 특정 시간 생성
t := time.Date(2024, time.January, 15, 10, 30, 0, 0, time.UTC)
fmt.Println(t) // 2024-01-15 10:30:00 +0000 UTC
// 시간 비교
past := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(now.After(past)) // true
fmt.Println(now.Before(past)) // false
fmt.Println(now.Equal(past)) // false
// 시간 경과
elapsed := time.Since(past) // now.Sub(past)와 동일
fmt.Printf("지난 시간: %.0f일\n", elapsed.Hours()/24)
// 특정 시간까지 남은 시간
future := time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC)
remaining := time.Until(future) // future.Sub(now)와 동일
fmt.Printf("남은 시간: %.0f일\n", remaining.Hours()/24)
}
Duration — 시간 간격
package main
import (
"fmt"
"time"
)
func main() {
// Duration 상수
d1 := 5 * time.Second // 5초
d2 := 2*time.Hour + 30*time.Minute // 2시간 30분
d3 := 100 * time.Millisecond // 100밀리초
fmt.Println(d1) // 5s
fmt.Println(d2) // 2h30m0s
fmt.Println(d3) // 100ms
// Duration 단위 변환
d := 90 * time.Minute
fmt.Printf("%.1f시간\n", d.Hours()) // 1.5시간
fmt.Printf("%.0f분\n", d.Minutes()) // 90분
fmt.Printf("%.0f초\n", d.Seconds()) // 5400초
// 시간 더하기/빼기
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
yesterday := now.Add(-24 * time.Hour)
fmt.Println("내일:", tomorrow.Format("2006-01-02"))
fmt.Println("어제:", yesterday.Format("2006-01-02"))
// 두 시간의 차이 (Duration)
diff := tomorrow.Sub(yesterday)
fmt.Println("차이:", diff) // 48h0m0s
// 성능 측정 패턴
start := time.Now()
// 작업 시뮬레이션
sum := 0
for i := 0; i < 1_000_000; i++ {
sum += i
}
elapsed := time.Since(start)
fmt.Printf("실행 시간: %v, 결과: %d\n", elapsed, sum)
}
시간 포맷과 파싱
Go의 시간 포맷은 독특합니다. 2006-01-02 15:04:05라는 기준 시간 을 사용해 레이아웃을 지정합니다.
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Go의 기준 시간: Mon Jan 2 15:04:05 MST 2006
// 기억법: 1 2 3 4 5 6 (월 일 시 분 초 연)
// 표준 레이아웃
fmt.Println(now.Format(time.RFC3339)) // 2024-01-15T10:30:00+09:00
fmt.Println(now.Format(time.RFC3339Nano)) // 나노초 포함
fmt.Println(now.Format(time.RFC1123)) // Mon, 15 Jan 2024 10:30:00 KST
fmt.Println(now.Format(time.DateOnly)) // 2024-01-15 (Go 1.20+)
fmt.Println(now.Format(time.TimeOnly)) // 10:30:00 (Go 1.20+)
// 커스텀 레이아웃
fmt.Println(now.Format("2006년 01월 02일")) // 2024년 01월 15일
fmt.Println(now.Format("15시 04분 05초")) // 10시 30분 00초
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2024-01-15 10:30:00
fmt.Println(now.Format("01/02/2006")) // 01/15/2024 (미국식)
fmt.Println(now.Format("Monday, January 2, 2006")) // Monday, January 15, 2024
fmt.Println(now.Format("3:04 PM")) // 10:30 AM
// 파싱 — 레이아웃 문자열과 입력 문자열의 형식이 일치해야 함
layout := "2006-01-02 15:04:05"
t, err := time.Parse(layout, "2024-01-15 10:30:00")
if err != nil {
fmt.Println("파싱 오류:", err)
return
}
fmt.Println("파싱된 시간:", t)
// RFC3339 파싱
t2, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00+09:00")
fmt.Println("RFC3339:", t2)
// 로컬 타임존으로 파싱
loc, _ := time.LoadLocation("Asia/Seoul")
t3, _ := time.ParseInLocation(layout, "2024-01-15 10:30:00", loc)
fmt.Println("서울 시간:", t3)
}
타임존 처리
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// UTC 변환
utc := now.UTC()
fmt.Println("UTC:", utc.Format(time.RFC3339))
// 타임존 로드
seoul, _ := time.LoadLocation("Asia/Seoul")
tokyo, _ := time.LoadLocation("Asia/Tokyo")
ny, _ := time.LoadLocation("America/New_York")
// 타임존 변환 (같은 순간, 다른 표현)
fmt.Println("서울:", now.In(seoul).Format("2006-01-02 15:04:05 MST"))
fmt.Println("도쿄:", now.In(tokyo).Format("2006-01-02 15:04:05 MST"))
fmt.Println("뉴욕:", now.In(ny).Format("2006-01-02 15:04:05 MST"))
// 고정 오프셋 타임존
kst := time.FixedZone("KST", 9*60*60) // UTC+9
t := time.Date(2024, 1, 15, 10, 30, 0, 0, kst)
fmt.Println("KST:", t.Format(time.RFC3339))
// Unix 타임스탬프
unixTs := now.Unix() // 초
unixMs := now.UnixMilli() // 밀리초 (Go 1.17+)
unixNs := now.UnixNano() // 나노초
fmt.Printf("Unix: %d, 밀리초: %d, 나노초: %d\n", unixTs, unixMs, unixNs)
// Unix 타임스탬프 → time.Time
t2 := time.Unix(unixTs, 0)
fmt.Println("복원:", t2.Format(time.RFC3339))
}
타이머와 티커
package main
import (
"fmt"
"time"
)
func main() {
// time.After — 지정 시간 후 채널에 시간 전송
fmt.Println("3초 후 실행...")
select {
case t := <-time.After(3 * time.Second):
fmt.Println("실행됨:", t.Format("15:04:05"))
}
// time.NewTimer — 취소 가능한 타이머
timer := time.NewTimer(2 * time.Second)
go func() {
<-timer.C
fmt.Println("타이머 완료")
}()
// 타이머 취소
if timer.Stop() {
fmt.Println("타이머 취소됨")
}
// time.Tick — 반복 신호 (취소 불가, 간단한 용도)
// 주의: time.Tick은 고루틴 누수 위험이 있음
// time.NewTicker — 취소 가능한 반복 타이머
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
count := 0
for t := range ticker.C {
count++
fmt.Printf("틱 #%d: %s\n", count, t.Format("15:04:05.000"))
if count >= 3 {
ticker.Stop()
break
}
}
fmt.Println("완료")
}
math 패키지 — 수학 함수
package main
import (
"fmt"
"math"
)
func main() {
// 수학 상수
fmt.Printf("Pi: %.10f\n", math.Pi) // 3.1415926536
fmt.Printf("E: %.10f\n", math.E) // 2.7182818285
fmt.Printf("Phi: %.10f\n", math.Phi) // 1.6180339887 (황금비)
fmt.Printf("Sqrt2: %.10f\n", math.Sqrt2) // 1.4142135624
fmt.Printf("MaxFloat64: %e\n", math.MaxFloat64)
fmt.Printf("SmallestNonzeroFloat64: %e\n", math.SmallestNonzeroFloat64)
fmt.Println("MaxInt:", math.MaxInt)
fmt.Println("MinInt:", math.MinInt)
// 기본 수학 함수
fmt.Printf("Abs(-3.14): %.2f\n", math.Abs(-3.14)) // 3.14
fmt.Printf("Ceil(3.2): %.0f\n", math.Ceil(3.2)) // 4
fmt.Printf("Floor(3.8): %.0f\n", math.Floor(3.8)) // 3
fmt.Printf("Round(3.5): %.0f\n", math.Round(3.5)) // 4
fmt.Printf("Trunc(3.9): %.0f\n", math.Trunc(3.9)) // 3
// 제곱근과 거듭제곱
fmt.Printf("Sqrt(16): %.0f\n", math.Sqrt(16)) // 4
fmt.Printf("Cbrt(27): %.0f\n", math.Cbrt(27)) // 3 (세제곱근)
fmt.Printf("Pow(2, 10): %.0f\n", math.Pow(2, 10)) // 1024
fmt.Printf("Pow10(3): %.0f\n", float64(math.Pow10(3))) // 1000
// 로그와 지수
fmt.Printf("Log(math.E): %.4f\n", math.Log(math.E)) // 1.0000 (자연로그)
fmt.Printf("Log2(8): %.4f\n", math.Log2(8)) // 3.0000
fmt.Printf("Log10(100): %.4f\n", math.Log10(100)) // 2.0000
fmt.Printf("Exp(1): %.4f\n", math.Exp(1)) // 2.7183 (e^1)
// 삼각함수 (라디안 단위)
fmt.Printf("Sin(π/2): %.4f\n", math.Sin(math.Pi/2)) // 1.0000
fmt.Printf("Cos(0): %.4f\n", math.Cos(0)) // 1.0000
fmt.Printf("Tan(π/4): %.4f\n", math.Tan(math.Pi/4)) // 1.0000
// 최대/최소
fmt.Printf("Max(3, 7): %.0f\n", math.Max(3, 7)) // 7
fmt.Printf("Min(3, 7): %.0f\n", math.Min(3, 7)) // 3
fmt.Printf("Mod(10, 3): %.0f\n", math.Mod(10, 3)) // 1
// 특수 값
fmt.Println("Inf:", math.IsInf(math.Inf(1), 1)) // true (양의 무한대)
fmt.Println("NaN:", math.IsNaN(math.NaN())) // true
fmt.Println("Inf > MaxFloat64:", math.Inf(1) > math.MaxFloat64) // true
// 각도 ↔ 라디안 변환
degrees := 45.0
radians := degrees * math.Pi / 180
fmt.Printf("45° = %.4f 라디안\n", radians)
fmt.Printf("Sin(45°) = %.4f\n", math.Sin(radians))
}
math/rand 패키지 — 난수 생성
기본 난수 생성 (Go 1.20+)
package main
import (
"fmt"
"math/rand"
)
func main() {
// Go 1.20+ — rand.New 없이 바로 사용 가능, 자동 시드
fmt.Println(rand.Int()) // 랜덤 int
fmt.Println(rand.Intn(100)) // [0, 100) 범위의 int
fmt.Println(rand.Float64()) // [0.0, 1.0) 범위의 float64
fmt.Println(rand.Float32()) // [0.0, 1.0) 범위의 float32
// 범위 지정 난수
min, max := 10, 50
n := min + rand.Intn(max-min)
fmt.Printf("범위 [%d, %d): %d\n", min, max, n)
// 슬라이스 셔플
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
rand.Shuffle(len(s), func(i, j int) {
s[i], s[j] = s[j], s[i]
})
fmt.Println("셔플:", s)
// 슬라이스에서 랜덤 요소 선택
fruits := []string{"사과", "바나나", "체리", "포도", "망고"}
picked := fruits[rand.Intn(len(fruits))]
fmt.Println("선택된 과일:", picked)
// 확률 기반 선택 (70% 확률)
for i := 0; i < 5; i++ {
if rand.Float64() < 0.7 {
fmt.Println("성공 (70% 확률)")
} else {
fmt.Println("실패 (30% 확률)")
}
}
}
재현 가능한 난수 — 시드 설정
package main
import (
"fmt"
"math/rand"
)
func main() {
// 고정 시드 — 매번 같은 결과 (테스트, 시뮬레이션에 유용)
r1 := rand.New(rand.NewSource(42))
fmt.Println(r1.Intn(100)) // 항상 같은 값
fmt.Println(r1.Intn(100)) // 항상 같은 값
r2 := rand.New(rand.NewSource(42))
fmt.Println(r2.Intn(100)) // r1과 동일한 첫 번째 값
fmt.Println(r2.Intn(100)) // r1과 동일한 두 번째 값
// 게임 맵 생성 — 시드로 재현 가능
generateMap(12345)
generateMap(12345) // 동일한 맵
generateMap(99999) // 다른 맵
}
func generateMap(seed int64) {
r := rand.New(rand.NewSource(seed))
fmt.Printf("맵 (시드 %d): ", seed)
for i := 0; i < 10; i++ {
terrain := []string{"평원", "산", "강", "숲"}
fmt.Printf("%s ", terrain[r.Intn(len(terrain))])
}
fmt.Println()
}
crypto/rand — 암호학적으로 안전한 난수
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"math/big"
)
func main() {
// 암호학적으로 안전한 랜덤 바이트
token := make([]byte, 32)
if _, err := rand.Read(token); err != nil {
panic(err)
}
fmt.Println("랜덤 토큰:", base64.URLEncoding.EncodeToString(token))
// 범위 내 암호학적 난수
max := big.NewInt(100)
n, err := rand.Int(rand.Reader, max)
if err != nil {
panic(err)
}
fmt.Println("암호학적 난수 [0, 100):", n)
// 보안 토큰 생성 함수
fmt.Println("API 키:", generateAPIKey(32))
fmt.Println("세션 ID:", generateAPIKey(16))
}
func generateAPIKey(length int) string {
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b)
}
실전 예제 1 — 작업 스케줄러
package main
import (
"fmt"
"sync"
"time"
)
// Job — 실행할 작업
type Job struct {
Name string
Interval time.Duration
Fn func()
}
// Scheduler — 주기적 작업 실행기
type Scheduler struct {
jobs []Job
tickers []*time.Ticker
wg sync.WaitGroup
stop chan struct{}
}
func NewScheduler() *Scheduler {
return &Scheduler{stop: make(chan struct{})}
}
func (s *Scheduler) AddJob(name string, interval time.Duration, fn func()) {
s.jobs = append(s.jobs, Job{name, interval, fn})
}
func (s *Scheduler) Start() {
for _, job := range s.jobs {
job := job // 루프 변수 캡처
ticker := time.NewTicker(job.Interval)
s.tickers = append(s.tickers, ticker)
s.wg.Add(1)
go func() {
defer s.wg.Done()
fmt.Printf("[스케줄러] '%s' 작업 등록 (간격: %v)\n", job.Name, job.Interval)
for {
select {
case t := <-ticker.C:
fmt.Printf("[%s] '%s' 작업 실행\n",
t.Format("15:04:05"), job.Name)
job.Fn()
case <-s.stop:
return
}
}
}()
}
}
func (s *Scheduler) Stop() {
close(s.stop)
for _, t := range s.tickers {
t.Stop()
}
s.wg.Wait()
fmt.Println("[스케줄러] 모든 작업 중지")
}
func main() {
scheduler := NewScheduler()
// 작업 등록
scheduler.AddJob("헬스체크", 1*time.Second, func() {
fmt.Println(" → 서버 상태: 정상")
})
scheduler.AddJob("캐시 갱신", 2*time.Second, func() {
fmt.Println(" → 캐시 데이터 갱신 완료")
})
scheduler.AddJob("통계 집계", 3*time.Second, func() {
fmt.Println(" → 시간별 통계 집계 완료")
})
scheduler.Start()
// 8초 후 스케줄러 종료
time.Sleep(8 * time.Second)
scheduler.Stop()
}
실전 예제 2 — 통계 계산기
package main
import (
"fmt"
"math"
"math/rand"
"sort"
)
// Stats — 기술 통계 결과
type Stats struct {
Count int
Sum float64
Mean float64
Median float64
StdDev float64
Min float64
Max float64
P95 float64 // 95 퍼센타일
}
func Calculate(data []float64) Stats {
if len(data) == 0 {
return Stats{}
}
n := len(data)
sorted := make([]float64, n)
copy(sorted, data)
sort.Float64s(sorted)
// 합계와 평균
sum := 0.0
for _, v := range data {
sum += v
}
mean := sum / float64(n)
// 표준편차
variance := 0.0
for _, v := range data {
diff := v - mean
variance += diff * diff
}
variance /= float64(n)
stdDev := math.Sqrt(variance)
// 중앙값
var median float64
if n%2 == 0 {
median = (sorted[n/2-1] + sorted[n/2]) / 2
} else {
median = sorted[n/2]
}
// 95 퍼센타일
p95Idx := int(math.Ceil(0.95*float64(n))) - 1
if p95Idx >= n {
p95Idx = n - 1
}
p95 := sorted[p95Idx]
return Stats{
Count: n,
Sum: math.Round(sum*100) / 100,
Mean: math.Round(mean*100) / 100,
Median: math.Round(median*100) / 100,
StdDev: math.Round(stdDev*100) / 100,
Min: sorted[0],
Max: sorted[n-1],
P95: p95,
}
}
func main() {
// 정규분포 형태의 데이터 생성 (Box-Muller 변환)
r := rand.New(rand.NewSource(42))
data := make([]float64, 1000)
for i := range data {
// 정규분포 난수 (평균=100, 표준편차=15)
u1 := r.Float64()
u2 := r.Float64()
z := math.Sqrt(-2*math.Log(u1)) * math.Cos(2*math.Pi*u2)
data[i] = 100 + 15*z
}
stats := Calculate(data)
fmt.Printf("=== 기술 통계 ===\n")
fmt.Printf("데이터 수: %d\n", stats.Count)
fmt.Printf("합계: %.2f\n", stats.Sum)
fmt.Printf("평균: %.2f\n", stats.Mean)
fmt.Printf("중앙값: %.2f\n", stats.Median)
fmt.Printf("표준편차: %.2f\n", stats.StdDev)
fmt.Printf("최솟값: %.2f\n", stats.Min)
fmt.Printf("최댓값: %.2f\n", stats.Max)
fmt.Printf("95 퍼센타일: %.2f\n", stats.P95)
}
패키지 요약
| 패키지 | 주요 타입/함수 | 특징 |
|---|---|---|
time | Time, Duration, Timer, Ticker | 타임존 지원, 모노토닉 클록 |
math | Sqrt, Pow, Sin, Log, 상수 | float64 기반 |
math/rand | Intn, Float64, Shuffle | 빠르지만 예측 가능 |
crypto/rand | Read, Int | 암호학적으로 안전, 느림 |
math/rand vs crypto/rand: 시뮬레이션, 게임, 테스트에는 math/rand를, 패스워드·토큰·세션 ID 등 보안이 중요한 곳에는 반드시 crypto/rand를 사용하세요.