horloges petites

This commit is contained in:
2026-08-30 11:05:00 +02:00
parent 783b6c3b60
commit 39620a8a66
6 changed files with 4781 additions and 5 deletions
Generated
+4377 -1
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -1,6 +1,10 @@
[package]
name = "horloge-bourses-mondiales"
name = "horloge-bourses-mondiales-gui"
version = "0.1.0"
edition = "2024"
edition = "2021"
[dependencies]
iced = { version = "0.13", features = ["canvas", "tokio"] }
tokio = { version = "1.0", features = ["time"] }
chrono = "0.4"
chrono-tz = "0.10"
+38
View File
@@ -0,0 +1,38 @@
# Horloge Bourses Mondiales (TUI)
Application terminal moderne (TUI) écrite en Rust permettant de suivre en temps réel l'état, l'heure locale, une horloge analogique et une horloge numérique de 10 places boursières majeures à travers le monde, ainsi que le temps universel (UTC).
## Fonctionnalités
- **Bandeau horizontal complet** affichant 10 horloges synchronisées.
- **Gestion dynamique des fuseaux horaires IANA** (incluant les changements d'heure été/hiver).
- **Statut d'ouverture en temps réel** (Calcul automatique des week-ends et des pauses déjeuner des marchés).
- **Horloge analogique et numérique** intégrées avec précision.
- **Actualisation automatique** chaque seconde.
- **Interface fluide et réactive** basée sur `ratatui` et `crossterm`.
## Marchés Affichés
0. **UTC** (Cyan)
1. **New York Stock Exchange (NYSE)**
2. **Shanghai Stock Exchange**
3. **Euronext Paris**
4. **Japan Exchange Group (Tokyo)**
5. **Shenzhen Stock Exchange**
6. **Hong Kong Exchanges and Clearing**
7. **TMX Group (Toronto)**
8. **London Stock Exchange**
9. **National Stock Exchange of India (NSE)**
## Prérequis
- Rust (édition stable actuelle)
- Cargo
## Installation & Compilation
Clonez ou placez-vous dans le répertoire du projet, puis compilez en mode release :
```bash
cargo build --release
```
+79
View File
@@ -0,0 +1,79 @@
use iced::mouse;
use iced::widget::canvas::{Canvas, Frame, Geometry, Path, Program, Stroke};
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
use chrono::{DateTime, Timelike};
use chrono_tz::Tz;
pub struct AnalogClock {
pub time: DateTime<Tz>,
}
impl<Message> Program<Message> for AnalogClock {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let mut frame = Frame::new(renderer, bounds.size());
let center = frame.center();
let radius = bounds.width.min(bounds.height) / 2.0 - 5.0;
if radius <= 0.0 {
return vec![frame.into_geometry()];
}
// Cadran circulaire
let dial = Path::circle(center, radius);
frame.stroke(&dial, Stroke::default().with_color(Color::WHITE).with_width(1.5));
let hour = self.time.hour() % 12;
let minute = self.time.minute();
let second = self.time.second();
// Calcul des angles précis
let second_angle = (second as f32 / 60.0) * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
let minute_angle = ((minute as f32 + second as f32 / 60.0) / 60.0) * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
let hour_angle = (((hour as f32 + minute as f32 / 60.0) / 12.0) * std::f32::consts::TAU) - std::f32::consts::FRAC_PI_2;
// Aiguille des heures (Bleu)
let hour_len = radius * 0.5;
let hour_end = Point::new(
center.x + hour_angle.cos() * hour_len,
center.y + hour_angle.sin() * hour_len,
);
let hour_line = Path::line(center, hour_end);
frame.stroke(&hour_line, Stroke::default().with_color(Color::from_rgb(0.2, 0.5, 1.0)).with_width(3.0));
// Aiguille des minutes (Blanc)
let min_len = radius * 0.75;
let min_end = Point::new(
center.x + minute_angle.cos() * min_len,
center.y + minute_angle.sin() * min_len,
);
let min_line = Path::line(center, min_end);
frame.stroke(&min_line, Stroke::default().with_color(Color::WHITE).with_width(2.0));
// Aiguille des secondes (Rouge)
let sec_len = radius * 0.85;
let sec_end = Point::new(
center.x + second_angle.cos() * sec_len,
center.y + second_angle.sin() * sec_len,
);
let sec_line = Path::line(center, sec_end);
frame.stroke(&sec_line, Stroke::default().with_color(Color::from_rgb(1.0, 0.2, 0.2)).with_width(1.0));
vec![frame.into_geometry()]
}
}
pub fn view_clock(time: DateTime<Tz>) -> Element<'static, crate::Message> {
Canvas::new(AnalogClock { time })
.width(Length::Fixed(90.0))
.height(Length::Fixed(90.0))
.into()
}
+155
View File
@@ -0,0 +1,155 @@
use chrono::{DateTime, Datelike, NaiveTime, Timelike, Utc};
use chrono_tz::Tz;
#[derive(Debug, Clone)]
pub struct TradingSession {
pub open: NaiveTime,
pub close: NaiveTime,
}
#[derive(Debug, Clone)]
pub struct Exchange {
pub name: String,
pub city: String,
pub timezone: Tz,
pub sessions: Vec<TradingSession>,
}
impl Exchange {
pub fn local_time(&self, utc_now: &DateTime<Utc>) -> DateTime<Tz> {
utc_now.with_timezone(&self.timezone)
}
pub fn is_open(&self, utc_now: &DateTime<Utc>) -> bool {
let local = self.local_time(utc_now);
let weekday = local.weekday().number_from_monday();
if weekday > 5 {
return false;
}
let time = local.time();
self.sessions.iter().any(|session| {
if session.open <= session.close {
time >= session.open && time <= session.close
} else {
time >= session.open || time <= session.close
}
})
}
}
pub fn get_exchanges() -> Vec<Exchange> {
vec![
Exchange {
name: "UTC".to_string(),
city: "UTC".to_string(),
timezone: chrono_tz::UTC,
sessions: vec![],
},
Exchange {
name: "NEW YORK STOCK EXCHANGE".to_string(),
city: "NEW YORK".to_string(),
timezone: chrono_tz::America::New_York,
sessions: vec![TradingSession {
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
}],
},
Exchange {
name: "SHANGHAI STOCK EXCHANGE".to_string(),
city: "SHANGHAI".to_string(),
timezone: chrono_tz::Asia::Shanghai,
sessions: vec![
TradingSession {
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
},
TradingSession {
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
},
],
},
Exchange {
name: "EURONEXT PARIS".to_string(),
city: "PARIS".to_string(),
timezone: chrono_tz::Europe::Paris,
sessions: vec![TradingSession {
open: NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
}],
},
Exchange {
name: "JAPAN EXCHANGE GROUP".to_string(),
city: "TOKYO".to_string(),
timezone: chrono_tz::Asia::Tokyo,
sessions: vec![
TradingSession {
open: NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
},
TradingSession {
open: NaiveTime::from_hms_opt(12, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
},
],
},
Exchange {
name: "SHENZHEN STOCK EXCHANGE".to_string(),
city: "SHENZHEN".to_string(),
timezone: chrono_tz::Asia::Shanghai,
sessions: vec![
TradingSession {
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
},
TradingSession {
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
},
],
},
Exchange {
name: "HONG KONG EXCHANGES".to_string(),
city: "HONG KONG".to_string(),
timezone: chrono_tz::Asia::Hong_Kong,
sessions: vec![
TradingSession {
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
},
TradingSession {
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
},
],
},
Exchange {
name: "TMX GROUP".to_string(),
city: "TORONTO".to_string(),
timezone: chrono_tz::America::Toronto,
sessions: vec![TradingSession {
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
}],
},
Exchange {
name: "LONDON STOCK EXCHANGE".to_string(),
city: "LONDON".to_string(),
timezone: chrono_tz::Europe::London,
sessions: vec![TradingSession {
open: NaiveTime::from_hms_opt(8, 0, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 30, 0).unwrap(),
}],
},
Exchange {
name: "NATIONAL STOCK EXCHANGE OF INDIA".to_string(),
city: "INDIA".to_string(),
timezone: chrono_tz::Asia::Kolkata,
sessions: vec![TradingSession {
open: NaiveTime::from_hms_opt(9, 15, 0).unwrap(),
close: NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
}],
},
]
}
+126 -2
View File
@@ -1,3 +1,127 @@
fn main() {
println!("Hello, world!");
mod exchange;
mod clock;
use exchange::{get_exchanges, Exchange};
use iced::widget::{column, container, row, text};
use iced::{Element, Length, Color, Theme, Subscription};
use chrono::{DateTime, Utc};
use std::time::Duration;
pub fn main() -> iced::Result {
iced::application(
"Horloges Boursières Mondiales",
WorldClocksApp::update,
WorldClocksApp::view,
)
.subscription(WorldClocksApp::subscription)
.window(iced::window::Settings {
size: iced::Size { width: 1400.0, height: 320.0 },
..Default::default()
})
.run_with(WorldClocksApp::new)
}
struct WorldClocksApp {
exchanges: Vec<Exchange>,
now: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum Message {
Tick(DateTime<Utc>),
}
impl WorldClocksApp {
fn new() -> (Self, iced::Task<Message>) {
(
Self {
exchanges: get_exchanges(),
now: Utc::now(),
},
iced::Task::none(),
)
}
fn update(&mut self, message: Message) {
match message {
Message::Tick(time) => {
self.now = time;
}
}
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(Duration::from_secs(1)).map(|_| Message::Tick(Utc::now()))
}
fn view(&self) -> Element<'_, Message> {
let mut row_layout = row![].spacing(10).padding(15);
for (i, exchange) in self.exchanges.iter().enumerate() {
let is_utc = i == 0;
let local_time = exchange.local_time(&self.now);
let time_str = local_time.format("%H:%M:%S").to_string();
let mut col = column![].spacing(5).align_x(iced::Alignment::Center);
if is_utc {
col = col.push(
text("UTC")
.size(14)
.style(|_theme: &Theme| text::Style { color: Some(Color::from_rgb(0.0, 0.8, 0.8)) })
);
col = col.push(text("").size(12));
} else {
col = col.push(text(exchange.city.clone()).size(13));
let is_open = exchange.is_open(&self.now);
let (status_text, status_color) = if is_open {
("ouvert", Color::from_rgb(0.0, 0.8, 0.2))
} else {
("fermé", Color::from_rgb(0.9, 0.2, 0.2))
};
col = col.push(
text(status_text)
.size(12)
.style(move |_theme: &Theme| text::Style { color: Some(status_color) })
);
}
// Horloge analogique graphique
col = col.push(clock::view_clock(local_time));
// Horloge numérique
let digital_color = if is_utc { Color::from_rgb(0.0, 0.8, 0.8) } else { Color::WHITE };
col = col.push(
text(time_str)
.size(13)
.style(move |_theme: &Theme| text::Style { color: Some(digital_color) })
);
let boxed_clock = container(col)
.width(Length::Fixed(125.0))
.padding(8)
.style(|_theme: &Theme| container::Style {
background: Some(iced::Background::Color(Color::from_rgb(0.12, 0.12, 0.15))),
border: iced::Border {
color: Color::from_rgb(0.25, 0.25, 0.3),
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
});
row_layout = row_layout.push(boxed_clock);
}
container(row_layout)
.width(Length::Fill)
.height(Length::Fill)
.center_x(Length::Fill)
.center_y(Length::Fill)
.style(|_theme: &Theme| container::Style {
background: Some(iced::Background::Color(Color::from_rgb(0.08, 0.08, 0.1))),
..Default::default()
})
.into()
}
}