Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0452211
feat: implement Error for ParseError
sunng87 Jan 21, 2026
4f20ffb
style: format code
sunng87 Jan 21, 2026
6ea2eba
style: format
sunng87 Jan 21, 2026
3bfab3e
fix: pluralization for postgres style
sunng87 Jan 21, 2026
db10de0
fix: several roundtrip cases
sunng87 Jan 21, 2026
81359a9
feat: implement postgres_verbose
sunng87 Jan 21, 2026
c89c533
ci: add github actions
sunng87 Jan 21, 2026
4843bb1
ci: update ci
sunng87 Jan 21, 2026
eec5c4d
ci: update arm runner
sunng87 Jan 21, 2026
d3cba01
feat: add parse_sql for interval
sunng87 Jan 21, 2026
aa519d5
Merge pull request #4 from sunng87/feature/parse-sql
sunng87 Jan 21, 2026
1c26cd2
Merge pull request #3 from sunng87/feature/postgres-verbose
sunng87 Jan 21, 2026
95d9f4b
Merge pull request #2 from sunng87/feature/parse-error
sunng87 Jan 21, 2026
9a1c365
Merge branch 'master' into feature/postgres-style
sunng87 Jan 21, 2026
17ca774
fix: format
sunng87 Jan 21, 2026
7e6ea8f
Merge pull request #1 from sunng87/feature/postgres-style
sunng87 Jan 21, 2026
29f6436
feat: from_postgres_verbose
sunng87 Jan 21, 2026
9e217a6
Merge pull request #5 from sunng87/feature/from-postgres-verbose
sunng87 Jan 21, 2026
bb2de2a
fix: iso format
sunng87 Jan 22, 2026
0f8d14c
fix: sql standard format for leading zero
sunng87 Jan 22, 2026
2192fad
fix: sql 0 output
sunng87 Jan 22, 2026
bd20f1c
fix: update pluar form of fractional sections
sunng87 Jan 22, 2026
18a56d2
Merge pull request #6 from sunng87/fix/iso-format
sunng87 Jan 22, 2026
6f582fb
chore: fork
sunng87 Jan 22, 2026
b8499f7
chore: Release pg_interval_2 version 0.5.1
sunng87 Jan 22, 2026
016e12a
fix: correct to_sql format
sunng87 Jan 23, 2026
541247a
Merge pull request #7 from sunng87/fix/sql_standard
sunng87 Jan 23, 2026
ee6917c
chore: Release pg_interval_2 version 0.5.2
sunng87 Jan 23, 2026
53b6f86
Merge branch 'master' into master
sunng87 Feb 5, 2026
a1fb277
Update README.md
sunng87 Feb 5, 2026
2f9996d
Update Cargo.toml
sunng87 Feb 5, 2026
a4f647f
Merge remote-tracking branch 'origin/master'
sunng87 Mar 12, 2026
767265f
chore: resolve lint issues
sunng87 Mar 12, 2026
98065ba
refactor: simplify sql format
sunng87 Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "pg_interval"
version = "0.4.4"
edition = "2021"
authors = ["Ryan Piper <piper.ryan235@gmail.com>"]
authors = ["Ryan Piper <piper.ryan235@gmail.com>", "Ning Sun <n@sunng.info>"]
license = "MIT"
description = "A native PostgreSQL interval type"
repository = "https://github.com/piperRyan/rust-postgres-interval"
Expand Down
17 changes: 0 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[![Build Status](https://travis-ci.org/piperRyan/rust-postgres-interval.svg?branch=master)](https://travis-ci.org/piperRyan/rust-postgres-interval) [![codecov](https://codecov.io/gh/piperRyan/rust-postgres-interval/branch/master/graph/badge.svg)](https://codecov.io/gh/piperRyan/rust-postgres-interval)

# Rust-Postgres-Interval
A interval type for the postgres driver.

Expand All @@ -23,18 +21,3 @@ fn main() {
assert_eq!(String::from("P1Y1M1DT1H"), output);
}
```

## Requirements
- rust 1.22

## Roadmap to 1.0.0

- [x] Convert Interval Into Formated String
- [x] Iso 8601
- [x] Postgres
- [x] Sql
- [ ] Parse Formated Strings Into The Interval Type
- [x] Iso 8601
- [x] Postgres
- [ ] Sql
- [x] Chrono Integrations
2 changes: 1 addition & 1 deletion src/integrations/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const NANOS_PER_MICRO: i64 = 1000;
impl Interval {
/// Tries to convert from the `Duration` type to a `Interval`. Will
/// return `None` on a overflow. This is a lossy conversion in that
/// any units smaller than a microsecond will be lost.
/// any units smaller than a microsecond will be lost.
pub fn from_duration(duration: Duration) -> Option<Interval> {
let mut days = duration.num_days();
let mut new_dur = duration - Duration::days(days);
Expand Down
19 changes: 13 additions & 6 deletions src/interval_fmt/iso_8601.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ impl IntervalNorm {
if self.minutes != 0 {
time_interval.push_str(&format!("{}M", self.minutes));
}
if self.seconds != 0 {
time_interval.push_str(&format!("{}S", self.seconds));
}
if self.microseconds != 0 {
let ms = super::safe_abs_u64(self.microseconds);
time_interval.push_str(&format!(".{:06}", ms));
if self.seconds != 0 || self.microseconds != 0 {
let secs_with_micros = if self.seconds != 0 && self.microseconds != 0 {
format!(
"{}.{:06}",
self.seconds,
super::safe_abs_u64(self.microseconds)
)
} else if self.microseconds != 0 {
format!(".{:06}", super::safe_abs_u64(self.microseconds))
} else {
format!("{}", self.seconds)
};
time_interval.push_str(&format!("{}S", secs_with_micros));
}
} else {
time_interval = "".to_owned();
Expand Down
151 changes: 148 additions & 3 deletions src/interval_fmt/postgres.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
use crate::interval_norm::IntervalNorm;

fn get_year_suffix(value: i32) -> &'static str {
if value == 1 {
"year"
} else {
"years"
}
}

fn get_mon_suffix(value: i32) -> &'static str {
if value == 1 {
"mon"
} else {
"mons"
}
}

fn get_day_suffix(value: i32) -> &'static str {
if value == 1 {
"day"
} else {
"days"
}
}

fn get_hour_suffix(value: i64) -> &'static str {
if value == 1 {
"hour"
} else {
"hours"
}
}

fn get_min_suffix(value: i64) -> &'static str {
if value == 1 {
"min"
} else {
"mins"
}
}

fn get_sec_suffix(seconds: i64, microseconds: i64) -> &'static str {
if seconds == 1 && microseconds == 0 {
"sec"
} else {
"secs"
}
}

impl IntervalNorm {
/// Produces a postgres compliant interval string.
pub fn into_postgres(self) -> String {
Expand All @@ -10,14 +58,18 @@ impl IntervalNorm {
let mut day_interval = "".to_owned();
let time_interval = self.get_postgres_time_interval();
if self.is_day_present() {
day_interval = format!("{:#?} days ", self.days)
day_interval = format!("{} {} ", self.days, get_day_suffix(self.days))
}
if self.is_year_month_present() {
if self.years != 0 {
year_interval.push_str(&format!("{:#?} year ", self.years))
year_interval.push_str(&format!("{} {} ", self.years, get_year_suffix(self.years)))
}
if self.months != 0 {
year_interval.push_str(&format!("{:#?} mons ", self.months));
year_interval.push_str(&format!(
"{} {} ",
self.months,
get_mon_suffix(self.months)
));
}
}
year_interval.push_str(&day_interval);
Expand Down Expand Up @@ -48,4 +100,97 @@ impl IntervalNorm {
}
time_interval
}

/// Produces a postgres_verbose compliant interval string.
pub fn into_postgres_verbose(self) -> String {
let is_negative = !self.is_time_interval_pos()
&& (self.years < 0
|| self.months < 0
|| self.days < 0
|| self.hours < 0
|| self.minutes < 0
|| self.seconds < 0
|| self.microseconds < 0);

let mut parts = Vec::new();

if self.years != 0 {
let abs_years = if self.years < 0 {
-self.years
} else {
self.years
};
parts.push(format!("{} {}", abs_years, get_year_suffix(abs_years)));
}

if self.months != 0 {
let abs_months = if self.months < 0 {
-self.months
} else {
self.months
};
parts.push(format!("{} {}", abs_months, get_mon_suffix(abs_months)));
}

if self.days != 0 {
let abs_days = if self.days < 0 { -self.days } else { self.days };
parts.push(format!("{} {}", abs_days, get_day_suffix(abs_days)));
}

if self.hours != 0 {
let abs_hours = if self.hours < 0 {
-self.hours
} else {
self.hours
};
parts.push(format!("{} {}", abs_hours, get_hour_suffix(abs_hours)));
}

if self.minutes != 0 {
let abs_minutes = if self.minutes < 0 {
-self.minutes
} else {
self.minutes
};
parts.push(format!("{} {}", abs_minutes, get_min_suffix(abs_minutes)));
}

if self.seconds != 0 || self.microseconds != 0 {
let abs_seconds = if self.seconds < 0 {
-self.seconds
} else {
self.seconds
};
let abs_micros = if self.microseconds < 0 {
-self.microseconds
} else {
self.microseconds
};
if abs_micros != 0 {
let secs_with_micros = abs_seconds as f64 + abs_micros as f64 / 1_000_000.0;
parts.push(format!(
"{} {}",
secs_with_micros,
get_sec_suffix(abs_seconds, abs_micros)
));
} else {
parts.push(format!(
"{} {}",
abs_seconds,
get_sec_suffix(abs_seconds, abs_micros)
));
}
}

if parts.is_empty() {
return "@ 0".to_owned();
}

let result = format!("@ {}", parts.join(" "));
if is_negative {
format!("{} ago", result)
} else {
result
}
}
}
143 changes: 91 additions & 52 deletions src/interval_fmt/sql.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,104 @@
use crate::interval_norm::IntervalNorm;

impl IntervalNorm {
fn format_time(
hours: i64,
minutes: i64,
seconds: i64,
microseconds: i64,
sign: bool,
) -> String {
let sign_str = if sign { "-" } else { "" };
let time_str = format!(
"{}{}:{:02}:{:02}",
sign_str,
super::safe_abs_u64(hours),
super::safe_abs_u64(minutes),
super::safe_abs_u64(seconds)
);

if microseconds != 0 {
format!("{}.{:06}", time_str, super::safe_abs_u64(microseconds))
} else {
time_str
}
}

pub fn into_sql(self) -> String {
if self.is_zeroed() {
"0".to_owned()
} else if !self.is_time_present() && !self.is_day_present() {
get_year_month(self.months, self.years, true)
} else if !self.is_time_present() && !self.is_year_month_present() {
format!("{} 0:00:00", self.days)
} else if !self.is_year_month_present() && !self.is_day_present() {
get_time_interval(
self.hours,
self.minutes,
self.seconds,
self.microseconds,
self.is_time_interval_pos(),
true,
let has_negative = self.has_negative();
let has_positive = self.has_positive();

let has_year_month = self.is_year_month_present();
let has_day_time = self.is_day_present() || self.is_time_present();

let sql_standard_value = !(has_negative && has_positive || has_year_month && has_day_time);

if !has_negative && !has_positive {
return "0".to_owned();
}

if !sql_standard_value {
let year_sign = if self.years < 0 || self.months < 0 {
'-'
} else {
'+'
};
let day_sign = if self.days < 0 { '-' } else { '+' };
let sec_sign = if self.hours < 0
|| self.minutes < 0
|| self.seconds < 0
|| self.microseconds < 0
{
'-'
} else {
'+'
};

let time_str = format!(
"{}{}:{:02}:{:02}",
sec_sign,
super::safe_abs_u64(self.hours),
super::safe_abs_u64(self.minutes),
super::safe_abs_u64(self.seconds)
);

let time_str = if self.microseconds != 0 {
format!("{}.{:06}", time_str, super::safe_abs_u64(self.microseconds))
} else {
time_str
};

format!(
"{}{}-{} {}{} {}",
year_sign,
self.years.abs(),
self.months.abs(),
day_sign,
self.days.abs(),
time_str
)
} else if has_year_month {
format!("{}-{}", self.years, super::safe_abs_u32(self.months))
} else if self.days != 0 {
format!(
"{} {}",
self.days,
Self::format_time(
self.hours,
self.minutes,
self.seconds,
self.microseconds,
false
)
)
} else {
let year_month = get_year_month(self.months, self.years, false);
let time_interval = get_time_interval(
Self::format_time(
self.hours,
self.minutes,
self.seconds,
self.microseconds,
self.is_time_interval_pos(),
false,
);
format!("{} {:+} {}", year_month, self.days, time_interval)
has_negative,
)
}
}
}

fn get_year_month(mons: i32, years: i32, is_only_year_month: bool) -> String {
let months = super::safe_abs_u32(mons);
if years == 0 || is_only_year_month {
format!("{}-{}", years, months)
} else {
format!("{:+}-{}", years, months)
}
}

fn get_time_interval(
hours: i64,
mins: i64,
secs: i64,
micros: i64,
is_time_interval_pos: bool,
is_only_time: bool,
) -> String {
let mut interval = "".to_owned();
if is_time_interval_pos && is_only_time {
interval.push_str(&format!("{}:{:02}:{:02}", hours, mins, secs));
} else {
let minutes = super::safe_abs_u64(mins);
let seconds = super::safe_abs_u64(secs);
interval.push_str(&format!("{:+}:{:02}:{:02}", hours, minutes, seconds));
}
if micros != 0 {
let microseconds = format!(".{:06}", super::safe_abs_u64(micros));
interval.push_str(&microseconds);
}
interval
}
Loading
Loading