두번째 공백의 위치를 FIND(” “,A2,FIND(” “,A2)+1)로 찾는데, FIND(” “,A2)+1)로 첫번째 공백 위치 다음부터 다시 공백의 위치를 찾으므로 두번째 공백의 위치인 6이 구해지고,
FIND(” “,A2)로 첫번째 공백의 위치를 빼는데, 그러면 6-3은 3이 되므로 2가 되도록 -1을 한 것입니다.
보기만 해도 복잡하죠?
(다) ‘아파트’ 분리하기
아파트는 두번째 공백 위치 다음부터 마지막까지이므로 오히려 간단합니다.
=MID(A2,FIND(” “,A2,FIND(” “,A2)+1)+1,10)
위 수식에서 FIND(” “,A2,FIND(” “,A2)+1)는 두번째 공백의 위치를 구하는 것이고, +1을 추가해서 다음 위치부터 가져오도록 하는데, 10개를 가져옵니다. 그러나, 10개라 하더라도 가져올 단어가 3개뿐이 안되기때문에 확인해보면 공백 없이 ‘아파트’만 표시됩니다.
(2) ‘선경’ 등 단어 합치기
(가) 방법 1
마찬가지로 공백을 추가하기 위해 rept(” “,8)을 사용하고, MidB를 이용해 8개만 가져오고, 단어를 합치기 위해 Concat함수를 사용합니다.
수식은 Concat 다음에 C2셀의 수식을 붙여 넣는데, MidB와 rept(” “,50)을 추가해야 합니다.
4.19를 A2셀에서 A6셀까지에서 찾아서, 일치하는 셀인 A3셀과 같은 위치의 B3셀 값이 주황색을 반환하는 것입니다. 이건 너무 단순한 경우입니다.
나. 셀이 비어 있는 경우 윗 셀값으로 채우기
N4셀에 =LOOKUP(ROW(C4:C23),ROW(C4:C23)/(C4:C23<>””),C4:C23)라고 입력하면 아래와 같이 C4:C23<>””로 빈 셀인지 체크해서 빈 셀이라면 윗셀 값으로 채웁니다. N5셀의 경우도 1, N13셀의 경우는 “소계”로 채웠습니다.
위 화면을 보면 Microsoft 365를 사용해서 결과값 영역에 파란 선이 둘러쳐져 있는데, 낮은 버전이라면 먼저 결과가 표시될 영역인 H4셀부터 H23셀까지를 선택한 다음 위 수식을 입력하고, Ctrl+Shift+Enter키를 눌러야 할 것입니다. 그러면 위 수식은 왼쪽과 오른쪽에 중괄호가 없는데, 낮은 버전의 경우는 중괄호가 표시될 것입니다.
다. SumProduct 함수를 이용한 수식 변경하기
(H24셀 수식 수정하기)
이제 H24셀의 수식에 위 Lookup함수를 이용한 수식을 추가하겠습니다.
(MOD(ROW(H$4:H$23),2)=0)라는 행이 짝수인 경우에 소계가 아닌 경우를 추가하면 됩니다.
따라서 조건은 (MOD(ROW(H$4:H$23),2)=0)*(LOOKUP(ROW($C$4:$C$23),ROW($C$4:$C$23)/($C$4:$C$23<>””),$C$4:$C$23)<>”소계”)가 됩니다.
기존 Lookup 수식에서 C4:C24 범위에 F4키를 눌러 절대 참조형식으로 수정했고, Lookup 수식 뒤에 <>”소계”를 추가해서 ‘소계’가 아닌 경우만 더하도록 했습니다.
timestamp: i64 : 이 값은 유닉스 타임스탬프(1970-01-01 00:00:00 UTC 기준의 초 단위 정수) 입니다.
2. Utc.timestamp_opt(timestamp, 0)
chrono::Utc는 UTC(협정 세계시) 타임존을 나타냅니다.
timestamp_opt(secs, nsecs)는 주어진 초(secs)와 나노초(nsecs)를 UTC 시각으로 변환하려고 시도합니다.
반환값은 -chrono::LocalResult> enum인데, 이에는 세 가지 경우가 있습니다. – Single(datetime) → 정상적으로 변환됨 – None → 변환 불가 – Ambiguous(, ) → 모호한 시간 (주로 로컬 타임존에서 섬머타임 전환 시 발생, UTC에서는 거의 없음)
3. match 표현식
chrono::LocalResult::Single(datetime)일 경우에는 변환된 datetime을 format(“%Y-%m-%d %H:%M:%S”)로 지정한 문자열 포맷(연-월-일 시:분:초)으로 변환하고,
그 밖의 경우(None, Ambiguous)는 “Invalid timestamp”라는 문자열을 반환합니다.
Ⅱ. tokio main 매크로에 대해 알아보기
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let symbols = [ "005930.KS", // 삼성전자 "AAPL", // Apple "TSLA", // Tesla "LIT", // Global X Lithium & Battery Tech ETF "BRK-B", // Berkshire Hathaway Class B "AMZN", // Amazon "O", // Realty Income Corporation "TQQQ", // ProShares UltraPro QQQ "XOM", // Exxon Mobil "WMT", // Walmart ];
println!("Fetching stock data...\n");
let results = fetch_multiple_stocks(&symbols).await;
for (i, result) in results.iter().enumerate() { let row_number = i + 1; match result { Ok(stock) => { println!("{:<3} {:<10} {:<35} {:<15.2} {:<8} {:<20}", row_number, stock.symbol, if stock.long_name.len() > 35 { format!("{}...", &stock.long_name[..32]) } else { stock.long_name.clone() }, stock.regular_market_price, stock.currency, format_timestamp(stock.regular_market_time) ); } Err(e) => { println!("{:<3} {:<10} {:<35} {:<15} {:<8} {:<20}", row_number, symbols[i], "Error fetching data", "N/A", "N/A", "N/A" ); } } }
enumerate()를 쓰면 (index, result) 튜플이 넘어옴. row_number = i + 1로 1씩 증가하는 번호를 출력.
match result: ① 성공(Ok(stock))하면 – stock(StockData 구조체)를 이용해 주식 데이터를 출력하는데, stock은 result가 성공했을 때 값입니다. – stock.symbol 값을 symbol로 출력하고, – stock.long_name은 너무 길면 앞의 32자만 출력하고, + “…” 붙여서 가독성을 유지하고, – stock.regular_market_price는 소수점 둘째 자리까지 표시하고,({:.2}), – 화폐 단위(currency)를 출력하고, – stock.regular_market_time은 format_timestamp 함수로 변환하여 표시함
② 실패(Err)하면 – 주어진 symbols[i] 심볼과 함께 “Error fetching data”, “N/A” 값들을 표시.
let client = reqwest::Client::new(); let response = client .get(&url) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .send() .await?; let text = response.text().await?; let yahoo_response: YahooResponse = serde_json::from_str(&text)?; if let Some(result) = yahoo_response.chart.result.first() { let meta = &result.meta;
Ok(StockData { symbol: symbol.to_string(), long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()), regular_market_price: meta.regular_market_price.unwrap_or(0.0), currency: meta.currency.clone(), regular_market_time: meta.regular_market_time.unwrap_or(0), }) } else { Err(format!("No data found for symbol: {}", symbol).into()) } }
for &symbol in symbols { let symbol_owned = symbol.to_string(); let handle = tokio::spawn(async move { fetch_stock_data(&symbol_owned).await }); handles.push(handle); }
let mut results = Vec::new(); for handle in handles { match handle.await { Ok(result) => results.push(result), Err(e) => results.push(Err(e.into())), } }
results }
이 함수는 여러 주식 종목(symbol)을 동시에 비동기로 조회(fetch) 하기 위해 tokio::spawn을 사용하는 구조입니다.
반환값: Result의 벡터 – 각 종목(symbol)에 대해 Ok(StockData) 또는 Err(에러)가 담긴 리스트. – 즉, 한 종목 실패해도 다른 종목은 결과를 받을 수 있음.
2. 주요 동작 흐름
가. handles 벡터 생성
let mut handles = Vec::new();
비동기 작업(태스크) 핸들을 저장해 둘 벡터.
나. 종목별 비동기 작업 생성
for &symbol in symbols { let symbol_owned = symbol.to_string(); // 소유권 있는 String으로 변환 let handle = tokio::spawn(async move { fetch_stock_data(&symbol_owned).await }); handles.push(handle); }
for 루프를 돌면서 각 종목 기호(&str)를 String으로 복사(to_string) → 이유: tokio::spawn의 async move 블록은 ‘static 라이프타임을 요구하기 때문. 원본 &str는 반복문이 끝나면 사라질 수 있으니, 안전하게 소유권 있는 String 사용.
tokio::spawn(…) → 배경(백그라운드)에서 새로운 비동기 태스크 생성
async move → 클로저에 캡처되는 값(symbol_owned)을 이동(move)시켜 사용.
결과: 각 종목을 조회하는 여러 비동기 태스크가 동시에 실행됨.
다. 모든 태스크 완료 대기
let mut results = Vec::new(); for handle in handles { match handle.await { Ok(result) => results.push(result), Err(e) => results.push(Err(e.into())), } }
handle.await → 해당 비동기 태스크가 끝날 때까지 대기.
handle.await의 반환값: – Ok(result) → 작업이 정상 종료 → result(= Result)를 results에 저장. – Err(e) → 태스크 자체가 패닉 또는 취소 → 에러를 Box로 변환해 저장.
라. 결과 반환
results
벡터에는 각 종목별 Result<StockData, Error>가 순서대로 저장됨.
마. 주의점
종목 수가 매우 많으면 동시에 많은 태스크가 실행되어 서버나 네트워크에 부하 발생 가능 → tokio::task::JoinSet이나 futures::stream::FuturesUnordered로 동시 실행 수를 제한하는 방법 고려 가능.
use reqwest; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tokio; use chrono::{DateTime, Utc, TimeZone};
가. use reqwest;
HTTP 클라이언트 라이브러리를 불러오는(import) 기능으로, Yahoo Finance API에서 주식 데이터를 가져오는 HTTP를 요청하기 위해 사용합니다. 예시: reqwest::get(“https://api.example.com”)
Cargo.toml에서는 dependency를 선언한 것이고, 사용하려면 다시 use를 해야 합니다.
나. use serde::{Deserialize, Serialize};
직렬화/역직렬화 라이브러리인 serde를 import 하는 구문으로, ::{Deserialize, Serialize}는 라이브러리의 전체를 가져오는 것이 아니라 Deserialize와 Serialize 트레이트(trait)만 가져오는 것입니다.
JSON 데이터를 Rust 구조체로 변환하거나, 그 반대 역확을 하며, 본 코드에서는 Yahoo API의 JSON 응답을 StockData 구조체로 변환하는데 사용합니다.
dependencies의 serde와 main.rs의 serde의 차이점을 알아보면 아래와 같습니다.
[Cargo.toml의 dependencies] serde = { version = "1.0", features = ["derive"] }는 serde 크레이트의 추가 기능인 derive를 활성화하는 것으로 #[derive(Serialize, Deserialize)] 매크로를 사용 가능하게 합니다.
[main.rs의 use] use serde::{Deserialize, Serialize};는 실제로 구조체에 적용할 트레이트인 Deserialize, Serialize 트레이트를 사용하기 위한 것입니다.
[main.rs의 struct 구문] // derive 기능이 활성화되어서 이 매크로 사용 가능 #[derive(Debug, Deserialize, Serialize)] struct StockData { symbol: String, price: f64, }
[3단계 구조] features = ["derive"]로 derive 기능을 활성화하고, use serde::{...}로 Deserialize, Serialize 트레이트를 가져오고, #[derive(...)]로 구조체에 실제 매크로를 적용하는 3단계 구조입니다.
다. use std::collections::HashMap;
키-값 쌍을 저장하는 해시맵을 import 하는 것인데, 현재 코드에서는 사용하지 않습니다. 지우고 실행해보니 문제없습니다.
claude.ai가 필요없으니 삭제를 해야 하는데, 삭제를 하지 않았네요.
cargo run을 하니 unused가 HashMap뿐만 아니라 DateTime, e도 있습니다.
use std::collections::HashMap;은 한 줄을 지우고,
use chrono::{DateTime, Utc, TimeZone};에서는 DateTime만 지우고,
error를 의미하는 e는 위 화면의 제안에 따라 _e로 바꾸고 실행하니 문제없이 깔끔하게 실행됩니다.
라. use tokio;
tokio라는 비동기 런타임 라이브러리를 임포트하는 구문으로 async/await를 이용해 여러 주식의 정보를 동시에 병렬로 가져오기가 가능해집니다. 따라서, 10개 주식을 순차적으로 가져오면 10초가 걸리는데, 병렬로 가져오면 1초뿐이 안걸립니다.
마. use chrono::{Utc, TimeZone};
chrono는 날짜/시간 처리 라이브러리로서 Unix 타임스탬프를 사람이 읽기 쉬운 날짜로 변환해줍니다. 예를 들어 1692345600를 “2023-08-18 12:00:00″로 바꿔줍니다.
Unix 타임 스탬프는 UTC 기준으로 1970.1.1부터의 누적된 초이며, UTC는 협정 세계시(Coordinated Universal Time의 약어)로서 그리니치 표준시 (GMT)의 후속 표준이라고 합니다.
2. struct
이 코드는 Yahoo Finance API에서 주식 정보를 받아오기 위한 **데이터 구조(Struct)**를 정의한 것입니다.
serde 라이브러리를 이용해 JSON 데이터 → Rust 구조체 변환(Deserialize)과 반대로 변환(Serialize)을 하기 위해 설계되어 있습니다.
postman 사이트에서 https://query1.finance.yahoo.com/v8/finance/chart/AAPL를 열어보면 아래와 같이 깔끔한 JSON 포맷의 데이터를 보여줍니다.
중괄호 안에 chart(key)가 있고, 그 안에 result와 대괄호(배열)가 있고, 다시 중괄호 다음에 meta가 있으며, 그 안에 우리가 얻고자 하는 currency, symbol, regularMarketPrice 등이 key: Value 쌍으로 담겨져 있습니다. 이에 따라 단계별로 struct를 만듭니다.
모방은 창조의 어머니인가요? claude.ai의 도움을 받아 만든 “Yahoo Finace API를 이용해서 주식 데이터를 가져오는 프로그램”을 살펴보겠습니다. Rust는 먼저 Cargo.toml 파일에서 가져올 크레이트(라이브러리)를 정의하고, main.rs에서 실행 코드를 구현합니다.
1. Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
dependencies 섹션에 reqwest, serde, serde_json, tokio 크레이트(crate, library)를 버전과 features를 이용해 지정합니다.
가. reqwest
request가 맞는 단어인데, reqwest로 약간 다른 점 주의해야 합니다. reqwest 크레이트는 편리하고 높은 수준의 HTTP 클라이언트를 제공합니다.
version은 크레이트의 버전을 지정하는 것은 알겠는데, features는 크레이트의 특정 기능(선택적 기능)을 활성화하거나 비활성화할 때 사용되며, 조건부 컴파일을 가능하게 하여, 필요한 기능만 컴파일하도록 하는 것입니다.
features = [“json”]은 “json” 기능을 켜서 JSON 직렬화/역직렬화 기능을 사용할 수 있게 하는 것입니다.
Cargo.toml을 저장하면 아래와 같이 오른쪽에 X 0.12.23이라고 표시되는데, 이것은 reqwest 최신 버전이 0.12.23이 최신인데, 0.11로 지정해서 그런 것입니다. 이 때 0.11에 커서를 대고 있으면
아래와 같이 버전 목록이 표시되는데, 맨 위에 0.12.23이 있으므로 클릭합니다.
그러면 버전이 자동으로 변경되고, X표시가 없어지고, 녹색 체크 표시로 바뀝니다.
나. serde
데이터 직렬화(Serialize) / 역직렬화(Deserialize) 라이브러리입니다. JSON, TOML, YAML 등 다양한 포맷과 Rust 구조체를 변환할 때 사용하며,
features = [“derive”]는
[derive(Serialize, Deserialize)] 어트리뷰트를 쓸 수 있게 해주는 것입니다.
다. serde_json
serde의 JSON 전용 확장판으로서, Rust 데이터와 JSON 문자열간에 변환을 가능하게 해줍니다.
let user = User { name: "Kim".into(), age: 30 }; let json_str = serde_json::to_string(&user)?; // 구조체 → JSON 문자열 let parsed: User = serde_json::from_str(&json_str)?; // JSON → 구조체
라. tokio
Rust의 비동기 런타임 (async/await 동작을 실제로 수행하는 엔진)으로서, features = [“full”]은 모든 기능(네트워킹, 파일 I/O, 타이머 등)을 한 번에 활성화하는 것이며, #[tokio::main] 매크로로 main 함수를 비동기로 만들 수 있습니다.
2. main.rs
가. 코드 1
아래 코드를 src 폴더의 main.rs를 연 후 Ctrl + A해서 전체를 선택한 후 Ctrl + V를 하면 기존 내용에 덮어씌워집니다.
use reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio;
#[derive(Debug, Deserialize, Serialize)]
struct YahooResponse {
chart: Chart,
}
#[derive(Debug, Deserialize, Serialize)]
struct Chart {
result: Vec<ChartResult>,
error: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize)]
struct ChartResult {
meta: Meta,
}
#[derive(Debug, Deserialize, Serialize)]
struct Meta {
currency: String,
symbol: String,
#[serde(rename = "longName")]
long_name: Option<String>,
#[serde(rename = "regularMarketPrice")]
regular_market_price: Option<f64>,
#[serde(rename = "regularMarketTime")]
regular_market_time: Option<i64>,
}
#[derive(Debug, Clone)]
struct StockData {
symbol: String,
long_name: String,
regular_market_price: f64,
currency: String,
regular_market_time: i64,
}
async fn fetch_stock_data(symbol: &str) -> Result<StockData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/{}", symbol);
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.send()
.await?;
let text = response.text().await?;
let yahoo_response: YahooResponse = serde_json::from_str(&text)?;
if let Some(result) = yahoo_response.chart.result.first() {
let meta = &result.meta;
Ok(StockData {
symbol: symbol.to_string(),
long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
regular_market_price: meta.regular_market_price.unwrap_or(0.0),
currency: meta.currency.clone(),
regular_market_time: meta.regular_market_time.unwrap_or(0),
})
} else {
Err(format!("No data found for symbol: {}", symbol).into())
}
}
async fn fetch_multiple_stocks(symbols: &[&str]) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>> {
let mut handles = Vec::new();
for &symbol in symbols {
let symbol_owned = symbol.to_string();
let handle = tokio::spawn(async move {
fetch_stock_data(&symbol_owned).await
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
match handle.await {
Ok(result) => results.push(result),
Err(e) => results.push(Err(e.into())),
}
}
results
}
fn format_timestamp(timestamp: i64) -> String {
use std::time::{UNIX_EPOCH, Duration};
let datetime = UNIX_EPOCH + Duration::from_secs(timestamp as u64);
// 간단한 포맷팅 (실제로는 chrono 크레이트 사용 권장)
format!("Unix timestamp: {}", timestamp)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let symbols = [
"005930.KS", // 삼성전자
"AAPL", // Apple
"TSLA", // Tesla
"LIT", // Global X Lithium & Battery Tech ETF
"BRK-B", // Berkshire Hathaway Class B
"AMZN", // Amazon
"O", // Realty Income Corporation
"TQQQ", // ProShares UltraPro QQQ
"XOM", // Exxon Mobil
"WMT", // Walmart
];
println!("Fetching stock data...\n");
let results = fetch_multiple_stocks(&symbols).await;
for (i, result) in results.iter().enumerate() {
match result {
Ok(stock) => {
println!("Symbol: {}", stock.symbol);
println!("Long Name: {}", stock.long_name);
println!("Regular Market Price: {:.2} {}", stock.regular_market_price, stock.currency);
println!("Currency: {}", stock.currency);
println!("Regular Market Time: {}", format_timestamp(stock.regular_market_time));
println!("---");
}
Err(e) => {
println!("Error fetching data for {}: {}", symbols[i], e);
println!("---");
}
}
}
Ok(())
}
cargo run을 하면 compile과 build를 한 후
Run을 하는데, 주식 정보 조회 결과를 주식별로 하나씩 보여주고, 시간이 Unix timestampt로 보여줘서 날짜와 시간을 알 수 없습니다.
나 코드 2
그래서 엑셀 처럼 표 형태로 보여주고, Unix time을 년월일시로 바꿔달라고 했더니 아래 코드가 되는데,
use reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio;
use chrono::{DateTime, Utc, TimeZone};
#[derive(Debug, Deserialize, Serialize)]
struct YahooResponse {
chart: Chart,
}
#[derive(Debug, Deserialize, Serialize)]
struct Chart {
result: Vec<ChartResult>,
error: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize)]
struct ChartResult {
meta: Meta,
}
#[derive(Debug, Deserialize, Serialize)]
struct Meta {
currency: String,
symbol: String,
#[serde(rename = "longName")]
long_name: Option<String>,
#[serde(rename = "regularMarketPrice")]
regular_market_price: Option<f64>,
#[serde(rename = "regularMarketTime")]
regular_market_time: Option<i64>,
}
#[derive(Debug, Clone)]
struct StockData {
symbol: String,
long_name: String,
regular_market_price: f64,
currency: String,
regular_market_time: i64,
}
async fn fetch_stock_data(symbol: &str) -> Result<StockData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/{}", symbol);
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.send()
.await?;
let text = response.text().await?;
let yahoo_response: YahooResponse = serde_json::from_str(&text)?;
if let Some(result) = yahoo_response.chart.result.first() {
let meta = &result.meta;
Ok(StockData {
symbol: symbol.to_string(),
long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
regular_market_price: meta.regular_market_price.unwrap_or(0.0),
currency: meta.currency.clone(),
regular_market_time: meta.regular_market_time.unwrap_or(0),
})
} else {
Err(format!("No data found for symbol: {}", symbol).into())
}
}
async fn fetch_multiple_stocks(symbols: &[&str]) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>> {
let mut handles = Vec::new();
for &symbol in symbols {
let symbol_owned = symbol.to_string();
let handle = tokio::spawn(async move {
fetch_stock_data(&symbol_owned).await
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
match handle.await {
Ok(result) => results.push(result),
Err(e) => results.push(Err(e.into())),
}
}
results
}
fn format_timestamp(timestamp: i64) -> String {
match Utc.timestamp_opt(timestamp, 0) {
chrono::LocalResult::Single(datetime) => datetime.format("%Y-%m-%d %H:%M:%S").to_string(),
_ => "Invalid timestamp".to_string(),
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let symbols = [
"005930.KS", // 삼성전자
"AAPL", // Apple
"TSLA", // Tesla
"LIT", // Global X Lithium & Battery Tech ETF
"BRK-B", // Berkshire Hathaway Class B
"AMZN", // Amazon
"O", // Realty Income Corporation
"TQQQ", // ProShares UltraPro QQQ
"XOM", // Exxon Mobil
"WMT", // Walmart
];
println!("Fetching stock data...\n");
let results = fetch_multiple_stocks(&symbols).await;
// 테이블 헤더 출력
println!("{:<3} {:<10} {:<35} {:<15} {:<8} {:<20}",
"No.", "Symbol", "Long Name", "Regular Price", "Currency", "Regular Market Time");
println!("{}", "-".repeat(95));
for (i, result) in results.iter().enumerate() {
let row_number = i + 1;
match result {
Ok(stock) => {
println!("{:<3} {:<10} {:<35} {:<15.2} {:<8} {:<20}",
row_number,
stock.symbol,
if stock.long_name.len() > 35 {
format!("{}...", &stock.long_name[..32])
} else {
stock.long_name.clone()
},
stock.regular_market_price,
stock.currency,
format_timestamp(stock.regular_market_time)
);
}
Err(e) => {
println!("{:<3} {:<10} {:<35} {:<15} {:<8} {:<20}",
row_number,
symbols[i],
"Error fetching data",
"N/A",
"N/A",
"N/A"
);
}
}
}
Ok(())
}
앞 부분에 use chrono::{DateTime, Utc, TimeZone};이 있어서
먼저 Cargo.toml에 chrono 크레이트를 추가해야 합니다.
chrono 버전에 커서를 갖다대보니 버전이 0.4.41로 표시되는데, 0.4 버전대이므로 그대로 둬도 문제 없습니다.
println!(“{name} is {age}”, name=”Alice”, age=30);
“Alice is 30”
<
왼쪽 정렬
{:<10}
“left “
>
오른쪽 정렬
{:>10}
” right”
^
가운데 정렬
{:^10}
” center “
fill
채움 문자 변경
{:*^10}”
“***hi****”
width
폭 지정
{:8}”
” 42″
width$
변수 폭 지정
{:width$}”
폭이 변수값
+
항상 부호 표시. 양수도 +, 음수는 – ※ 기본은 음수만 – 표시하고, 양수는 미표시
{:+}”
+42
‘ ‘(공백)
(열 맞추기 위해) 양수일 때 공백 한 칸 추가
println!(“{: }”, 42);
” 42″
#
진번 접두사 표시
#{:#x}”
“0x2a”
0
제로 패딩(0으로 채움)
println!(“{:08.2}”, 3.14159);
“00003.14” (폭 8, 소수점 2자리)
.precision
소수점 자리수
{:.2}
“3.14”
3. 천 단위 구분 쉼표
Rust 표준 라이브러리에는 없으므로 외부 크레이트 또는 수동 구현이 필요합니다.
가. num-format (다국어 지원)
[dependencies] num-format = "0.4"
use num_format::{Locale, ToFormattedString};
fn main() { let n = 123456789; println!("{}", n.to_formatted_string(&Locale::en)); // 123,456,789 println!("{}", n.to_formatted_string(&Locale::de)); // 123.456.789 }
나. separators (경량)
[dependencies] separators = "0.4"
use separators::Separatable;
fn main() { let n = 123456789; println!("{}", n.separated_string()); // 123,456,789 }
다. 직접 구현
fn format_with_commas(n: i64) -> String { let s = n.to_string(); let mut chars: Vec<char> = s.chars().rev().collect(); let mut result = String::new(); for (i, c) in chars.iter().enumerate() { if i != 0 && i % 3 == 0 { result.push(','); } result.push(*c); } result.chars().rev().collect() }